/* -----------------------------------------------------------------------
 * formatting.c
 *
 * src/backend/utils/adt/formatting.c
 *
 *
 *	 Portions Copyright (c) 1999-2022, PostgreSQL Global Development Group
 *
 *
 *	 TO_CHAR(); TO_TIMESTAMP(); TO_DATE(); TO_NUMBER();
 *
 *	 PostgreSQL 的时间戳/int/浮点数/数字格式化例程，
 *	 灵感来源于 Oracle TO_CHAR() / TO_DATE() / TO_NUMBER() 例程。
 *
 *
 *	 缓存和内存：
 *	例程使用（自身的）内部缓存用于格式图。
 *
 *	缓存使用静态缓冲区并在事务间保持持久。如果
 *	格式图大于缓存缓冲区，则解析器会始终被调用。
 *
 *	数字版本的注意事项：
 *	在这一版本中，所有内容都被实现为关键字（=> 不使用
 *	后缀），因为格式图仅适用于 *一个* 项目（数字）。
 *	这与时间戳版本不同，后者的每个关键字（可以）
 *	有后缀。
 *
 *	时间戳例程的注意事项：
 *	在此模块中，POSIX 的 'struct tm' 类型 *不* 被使用，而是
 *	PgSQL 类型，tm_mon 基于 1 (*非零*) 并且
 *	年份 *不* 基于 1900，而是使用完整的年份数字。
 *	模块支持公元前 / 公元 / 上午 / 下午。
 *
 *	to_char() 支持的类型：
 *
 *		时间戳、数字、int4、int8、float4、float8
 *
 *	反向转换支持的类型：
 *
 *		时间戳	- to_timestamp()
 *		日期		- to_date()
 *		数字		- to_number()
 *
 *
 *	Karel Zak
 *
 * TODO
 *	- 更好的数字构建（格式化）/ 解析，现在这不是
 *		  理想代码
 *	- 使用 Assert()
 *	- 添加对罗马数字到标准数字转换的支持
 *	- 添加对数字拼写的支持
 *	- 添加对字符串到字符串格式化的支持（我们必须做得更好
 *	  比 Oracle :-),
 *		to_char('Hello', 'X X X X X') -> 'H e l l o'
 *
 * -----------------------------------------------------------------------
 */

#ifdef DEBUG_TO_FROM_CHAR
#define DEBUG_elog_output	DEBUG3
#endif

#include "postgres.h"

#include <ctype.h>
#include <unistd.h>
#include <math.h>
#include <float.h>
#include <limits.h>

/*
 * towlower()及其他函数应在<wctype.h>中，但某些C99之前的系统
 * 在<wchar.h>中声明它们，因此也包括该头文件。
 */
#include <wchar.h>
#ifdef HAVE_WCTYPE_H
#include <wctype.h>
#endif

#ifdef USE_ICU
#include <unicode/ustring.h>
#endif

#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "mb/pg_wchar.h"
#include "parser/scansup.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/float.h"
#include "utils/formatting.h"
#include "utils/memutils.h"
#include "utils/numeric.h"
#include "utils/pg_locale.h"

/* ----------
 * 错误处理的便利宏
 * ----------
 *
 * 以下两个宏帮助处理需要
 * 'bool *have_error'参数的函数中的错误。当该参数不为NULL时，预计
 * 函数将在可能情况下抑制ereports。相反，它应该返回某个默认值并设置*have_error标志。
 *
 * RETURN_ERROR()宏旨在包装ereport()调用。当have_error
 * 函数参数不为NULL时，则不进行ereport，而是设置
 * *have_error标志并跳转到on_error标签。假定跳转
 * 资源将被释放并返回某个“默认”值。
 *
 * CHECK_ERROR()在*have_error标志被定义并设置时跳转到on_error标签。
 * 它应该用于在调用具有'bool *have_error'参数的其他函数后立即在错误时退出函数。
 */
#define RETURN_ERROR(throw_error) \
do { \
	if (fc_have_error) \
	{ \
		*fc_have_error = true; \
		goto on_error; \
	} \
	else \
	{ \
		throw_error; \
	} \
} while (0)

#define CHECK_ERROR \
do { \
	if (fc_have_error && *fc_have_error) \
		goto on_error; \
} while (0)

/* ----------
 * 例程标志
 * ----------
 */
#define DCH_FLAG		0x1		/* 日期-时间 标志	*/
#define NUM_FLAG		0x2		/* 数字 标志	*/
#define STD_FLAG		0x4		/* 标准 标志	*/

/* ----------
 * 关键字索引（从位置 32 (' ') 到 126 (~) 的 ASCII）
 * ----------
 */
#define KeyWord_INDEX_SIZE		('~' - ' ')
#define KeyWord_INDEX_FILTER(_c)	((_c) <= ' ' || (_c) >= '~' ? 0 : 1)

/* ----------
 * 单个节点的最大长度
 * ----------
 */
#define DCH_MAX_ITEM_SIZ	   12	/* 最大本地化日期名称		*/
#define NUM_MAX_ITEM_SIZ		8	/* 罗马数字 (RN 由 15 个字符组成)	*/


/* ----------
 * 格式解析器结构
 * ----------
 */
typedef struct
{
	const char *name;			/* 后缀字符串		*/
	int			len,			/* 后缀长度		*/
				id,				/* 在节点->后缀中使用 */
				type;			/* 前缀 / 后缀		*/
} KeySuffix;

/* ----------
 * FromCharDateMode
 * ----------
 *
 * 此值用于标记关键字可以属于的几种不同（并且相互排斥的）日期约定之一。
 */
typedef enum
{
	FROM_CHAR_DATE_NONE = 0,	/* 值不影响日期模式。 */
	FROM_CHAR_DATE_GREGORIAN,	/* 公历（天、月、年）样式日期 */
	FROM_CHAR_DATE_ISOWEEK		/* ISO 8601 周日期 */
} FromCharDateMode;

typedef struct
{
	const char *name;
	int			len;
	int			id;
	bool		is_digit;
	FromCharDateMode date_mode;
} KeyWord;

typedef struct
{
	uint8		type;			/* NODE_TYPE_XXX，见下文 */
	char		character[MAX_MULTIBYTE_CHAR_LEN + 1];	/* 如果类型是字符 */
	uint8		suffix;			/* 关键字前缀/后缀代码（如果有） */
	const KeyWord *key;			/* 如果类型是动作 */
} FormatNode;

#define NODE_TYPE_END		1
#define NODE_TYPE_ACTION	2
#define NODE_TYPE_CHAR		3
#define NODE_TYPE_SEPARATOR	4
#define NODE_TYPE_SPACE		5

#define SUFFTYPE_PREFIX		1
#define SUFFTYPE_POSTFIX	2

#define CLOCK_24_HOUR		0
#define CLOCK_12_HOUR		1


/* ----------
 * 完整月份
 * ----------
 */
static const char *const months_full[] = {
	"January", "February", "March", "April", "May", "June", "July",
	"August", "September", "October", "November", "December", NULL
};

static const char *const days_short[] = {
	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", NULL
};

/* ----------
 * 公元 / 公元前
 * ----------
 *	公元没有 0。年份从公元前 1 到公元 1，因此我们将其
 *	变为正数并将年份 == -1 映射到零年，并将所有负
 *	年份上移一。对于区间年份，我们仅返回年份。
 */
#define ADJUST_YEAR(year, is_interval)	((is_interval) ? (year) : ((year) <= 0 ? -((year) - 1) : (year)))

#define A_D_STR		"A.D."
#define a_d_STR		"a.d."
#define AD_STR		"AD"
#define ad_STR		"ad"

#define B_C_STR		"B.C."
#define b_c_STR		"b.c."
#define BC_STR		"BC"
#define bc_STR		"bc"

/*
 * 用于 seq_search 的公元 / 公元前 字符串。
 *
 * 这些以两种形式给出，带有句点的长形式和不带句点的标准
 * 形式。
 *
 * 数组的布局使得公元的匹配项具有偶数索引，而公元前的匹配项
 * 具有奇数索引。因此，公元前的布尔值通过
 * 取匹配项的数组索引，对 2 取模得出。
 */
static const char *const adbc_strings[] = {ad_STR, bc_STR, AD_STR, BC_STR, NULL};
static const char *const adbc_strings_long[] = {a_d_STR, b_c_STR, A_D_STR, B_C_STR, NULL};

/* ----------
 * 上午 / 下午
 * ----------
 */
#define A_M_STR		"A.M."
#define a_m_STR		"a.m."
#define AM_STR		"AM"
#define am_STR		"am"

#define P_M_STR		"P.M."
#define p_m_STR		"p.m."
#define PM_STR		"PM"
#define pm_STR		"pm"

/*
 * 用于 seq_search 的 上午 / 下午 字符串。
 *
 * 这些以两种形式给出，带有句点的长形式和不带句点的标准
 * 形式。
 *
 * 数组的布局使得上午的匹配项具有偶数索引，而下午的匹配项
 * 具有奇数索引。因此，下午的布尔值通过
 * 取匹配项的数组索引，对 2 取模得出。
 */
static const char *const ampm_strings[] = {am_STR, pm_STR, AM_STR, PM_STR, NULL};
static const char *const ampm_strings_long[] = {a_m_STR, p_m_STR, A_M_STR, P_M_STR, NULL};

/* ----------
 * 罗马数字中的月份
 * （必须按反向顺序排列以便 seq_search（在 FROM_CHAR 中），因为
 *	'VIII' 必须优先于 'V'）
 * ----------
 */
static const char *const rm_months_upper[] =
{"XII", "XI", "X", "IX", "VIII", "VII", "VI", "V", "IV", "III", "II", "I", NULL};

static const char *const rm_months_lower[] =
{"xii", "xi", "x", "ix", "viii", "vii", "vi", "v", "iv", "iii", "ii", "i", NULL};

/* ----------
 * 罗马数字
 * ----------
 */
static const char *const rm1[] = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", NULL};
static const char *const rm10[] = {"X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC", NULL};
static const char *const rm100[] = {"C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM", NULL};

/* ----------
 * 序数后缀
 * ----------
 */
static const char *const numTH[] = {"ST", "ND", "RD", "TH", NULL};
static const char *const numth[] = {"st", "nd", "rd", "th", NULL};

/* ----------
 * 标志与选项：
 * ----------
 */
#define TH_UPPER		1
#define TH_LOWER		2

/**********
 * 数字描述结构
 * ----------
 */
typedef struct
{
	int			pre,			/* （计数）小数点之前的数字 */
				post,			/* （计数）小数点之后的数字 */
				lsign,			/* 想要的区域符号 */
				flag,			/* 数字参数 */
				pre_lsign_num,	/* lsign 的临时值 */
				multi,			/* 'V' 的倍数 */
				zero_start,		/* 第一个零的位置 */
				zero_end,		/* 最后一个零的位置 */
				need_locale;	/* 需要它的区域设置 */
} NUMDesc;

/* ----------
 * NUMBER 版本的标志
 * ----------
 */
#define NUM_F_DECIMAL		(1 << 1)
#define NUM_F_LDECIMAL		(1 << 2)
#define NUM_F_ZERO			(1 << 3)
#define NUM_F_BLANK			(1 << 4)
#define NUM_F_FILLMODE		(1 << 5)
#define NUM_F_LSIGN			(1 << 6)
#define NUM_F_BRACKET		(1 << 7)
#define NUM_F_MINUS			(1 << 8)
#define NUM_F_PLUS			(1 << 9)
#define NUM_F_ROMAN			(1 << 10)
#define NUM_F_MULTI			(1 << 11)
#define NUM_F_PLUS_POST		(1 << 12)
#define NUM_F_MINUS_POST	(1 << 13)
#define NUM_F_EEEE			(1 << 14)

#define NUM_LSIGN_PRE	(-1)
#define NUM_LSIGN_POST	1
#define NUM_LSIGN_NONE	0

/* ----------
 * 测试
 * ----------
 */
#define IS_DECIMAL(_f)	((_f)->flag & NUM_F_DECIMAL)
#define IS_LDECIMAL(_f) ((_f)->flag & NUM_F_LDECIMAL)
#define IS_ZERO(_f) ((_f)->flag & NUM_F_ZERO)
#define IS_BLANK(_f)	((_f)->flag & NUM_F_BLANK)
#define IS_FILLMODE(_f) ((_f)->flag & NUM_F_FILLMODE)
#define IS_BRACKET(_f)	((_f)->flag & NUM_F_BRACKET)
#define IS_MINUS(_f)	((_f)->flag & NUM_F_MINUS)
#define IS_LSIGN(_f)	((_f)->flag & NUM_F_LSIGN)
#define IS_PLUS(_f) ((_f)->flag & NUM_F_PLUS)
#define IS_ROMAN(_f)	((_f)->flag & NUM_F_ROMAN)
#define IS_MULTI(_f)	((_f)->flag & NUM_F_MULTI)
#define IS_EEEE(_f)		((_f)->flag & NUM_F_EEEE)

/* ----------
 * 格式图片缓存
 *
 * 我们将缓存最长可达 DCH_CACHE_SIZE 字节的日期时间格式图片；
 * 同样，数字格式图片最长可达 NUM_CACHE_SIZE 字节。
 *
 * 简单起见，缓存条目是固定大小的，因此它们允许每个字符串中一个字节的 FormatNode 的最坏情况。
 *
 * CACHE_SIZE 常量的计算方式是为了使 sizeof(DCHCacheEntry) 和
 * sizeof(NUMCacheEntry) 为 2 的幂，或者稍微少于该值，以便于
 * 我们不浪费太多空间，逐个分配它们。 如果您向这些结构添加字段，请务必
 * 调整这些宏。
 *
 * 每个缓存中的最大条目数是 DCH_CACHE_ENTRIES
 * 和 NUM_CACHE_ENTRIES。
 * ----------
 */
#define DCH_CACHE_OVERHEAD \
	MAXALIGN(sizeof(bool) + sizeof(int))
#define NUM_CACHE_OVERHEAD \
	MAXALIGN(sizeof(bool) + sizeof(int) + sizeof(NUMDesc))

#define DCH_CACHE_SIZE \
	((2048 - DCH_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)
#define NUM_CACHE_SIZE \
	((1024 - NUM_CACHE_OVERHEAD) / (sizeof(FormatNode) + sizeof(char)) - 1)

#define DCH_CACHE_ENTRIES	20
#define NUM_CACHE_ENTRIES	20

typedef struct
{
	FormatNode	format[DCH_CACHE_SIZE + 1];
	char		str[DCH_CACHE_SIZE + 1];
	bool		std;
	bool		valid;
	int			age;
} DCHCacheEntry;

typedef struct
{
	FormatNode	format[NUM_CACHE_SIZE + 1];
	char		str[NUM_CACHE_SIZE + 1];
	bool		valid;
	int			age;
	NUMDesc		Num;
} NUMCacheEntry;

/* 日期/时间格式图片的全局缓存 */
static DCHCacheEntry *DCHCache[DCH_CACHE_ENTRIES];
static int	n_DCHCache = 0;		/* 当前条目数量 */
static int	DCHCounter = 0;		/* 老化事件计数器 */

/* 数字格式图片的全局缓存 */
static NUMCacheEntry *NUMCache[NUM_CACHE_ENTRIES];
static int	n_NUMCache = 0;		/* 当前条目数量 */
static int	NUMCounter = 0;		/* 老化事件计数器 */

/* ----------
 * 对于字符->日期/时间转换
 * ----------
 */
typedef struct
{
	FromCharDateMode mode;
	int			hh,
				pm,
				mi,
				ss,
				ssss,
				d,				/* 存储为 1-7，星期日 = 1，0表示缺失 */
				dd,
				ddd,
				mm,
				ms,
				year,
				bc,
				ww,
				w,
				cc,
				j,
				us,
				yysz,			/* 是 YY 还是 YYYY？ */
				clock,			/* 12小时还是24小时制？ */
				tzsign,			/* 如果没有时区信息，则为 +1，-1 或 0 */
				tzh,
				tzm,
				ff;				/* 小数精度 */
} TmFromChar;

#define ZERO_tmfc(_X) memset(_X, 0, sizeof(TmFromChar))

/* ----------
 * 调试
 * ----------
 */
#ifdef DEBUG_TO_FROM_CHAR
#define DEBUG_TMFC(_X) \
		elog(DEBUG_elog_output, "TMFC:\nmode %d\nhh %d\npm %d\nmi %d\nss %d\nssss %d\nd %d\ndd %d\nddd %d\nmm %d\nms: %d\nyear %d\nbc %d\nww %d\nw %d\ncc %d\nj %d\nus: %d\nyysz: %d\nclock: %d", \
			(_X)->mode, (_X)->hh, (_X)->pm, (_X)->mi, (_X)->ss, (_X)->ssss, \
			(_X)->d, (_X)->dd, (_X)->ddd, (_X)->mm, (_X)->ms, (_X)->year, \
			(_X)->bc, (_X)->ww, (_X)->w, (_X)->cc, (_X)->j, (_X)->us, \
			(_X)->yysz, (_X)->clock)
#define DEBUG_TM(_X) \
		elog(DEBUG_elog_output, "TM:\nsec %d\nyear %d\nmin %d\nwday %d\nhour %d\nyday %d\nmday %d\nnisdst %d\nmon %d\n",\
			(_X)->tm_sec, (_X)->tm_year,\
			(_X)->tm_min, (_X)->tm_wday, (_X)->tm_hour, (_X)->tm_yday,\
			(_X)->tm_mday, (_X)->tm_isdst, (_X)->tm_mon)
#else
#define DEBUG_TMFC(_X)
#define DEBUG_TM(_X)
#endif

/* ----------
 * 日期时间到字符转换
 *
 * 为了支持间隔以及时间戳，我们使用一个自定义的 "tm" 结构，
 * 它几乎就像 struct pg_tm，但有一个 64 位的 tm_hour 字段。
 * 我们省略了 tm_isdst 和 tm_zone 字段，这些在这里没有使用。
 * ----------
 */
struct fmt_tm
{
	int			tm_sec;
	int			tm_min;
	int64		tm_hour;
	int			tm_mday;
	int			tm_mon;
	int			tm_year;
	int			tm_wday;
	int			tm_yday;
	long int	tm_gmtoff;
};

typedef struct TmToChar
{
	struct fmt_tm tm;			/* 几乎是经典的 'tm' 结构 */
	fsec_t		fsec;			/* 小数秒 */
	const char *tzn;			/* timezone */
} TmToChar;

#define tmtcTm(_X)	(&(_X)->tm)
#define tmtcTzn(_X) ((_X)->tzn)
#define tmtcFsec(_X)	((_X)->fsec)

/* 注意：这用于将 pg_tm 复制到 fmt_tm，因此并非完全位级复制 */
#define COPY_tm(_DST, _SRC) \
do {	\
	(_DST)->tm_sec = (_SRC)->tm_sec; \
	(_DST)->tm_min = (_SRC)->tm_min; \
	(_DST)->tm_hour = (_SRC)->tm_hour; \
	(_DST)->tm_mday = (_SRC)->tm_mday; \
	(_DST)->tm_mon = (_SRC)->tm_mon; \
	(_DST)->tm_year = (_SRC)->tm_year; \
	(_DST)->tm_wday = (_SRC)->tm_wday; \
	(_DST)->tm_yday = (_SRC)->tm_yday; \
	(_DST)->tm_gmtoff = (_SRC)->tm_gmtoff; \
} while(0)

/* 注意：这用于将 pg_tm 和 fmt_tm 结构都归零 */
#define ZERO_tm(_X) \
do {	\
	memset(_X, 0, sizeof(*(_X))); \
	(_X)->tm_mday = (_X)->tm_mon = 1; \
} while(0)

#define ZERO_tmtc(_X) \
do { \
	ZERO_tm( tmtcTm(_X) ); \
	tmtcFsec(_X) = 0; \
	tmtcTzn(_X) = NULL; \
} while(0)

/*
 *	to_char(time) 在 to_char() 中被视为一个间隔，因此这个检查
 *	实际上是针对间隔和时间数据类型的。
 */
#define INVALID_FOR_INTERVAL  \
do { \
	if (fc_is_interval) \
		ereport(ERROR, \
				(errcode(ERRCODE_INVALID_DATETIME_FORMAT), \
				 errmsg("invalid format specification for an interval value"), \
				 errhint("Intervals are not tied to specific calendar dates."))); \
} while(0)

/*****************************************************************************
 *			关键字定义
 *****************************************************************************/

/* ----------
 * 后缀（FormatNode.suffix是这些代码的或）
 * ----------
 */
#define DCH_S_FM	0x01
#define DCH_S_TH	0x02
#define DCH_S_th	0x04
#define DCH_S_SP	0x08
#define DCH_S_TM	0x10

/* ----------
 * 后缀测试
 * ----------
 */
#define S_THth(_s)	((((_s) & DCH_S_TH) || ((_s) & DCH_S_th)) ? 1 : 0)
#define S_TH(_s)	(((_s) & DCH_S_TH) ? 1 : 0)
#define S_th(_s)	(((_s) & DCH_S_th) ? 1 : 0)
#define S_TH_TYPE(_s)	(((_s) & DCH_S_TH) ? TH_UPPER : TH_LOWER)

/* Oracle切换FM行为，我们不这样做；请参阅文档。 */
#define S_FM(_s)	(((_s) & DCH_S_FM) ? 1 : 0)
#define S_SP(_s)	(((_s) & DCH_S_SP) ? 1 : 0)
#define S_TM(_s)	(((_s) & DCH_S_TM) ? 1 : 0)

/* ----------
 * DATE-TIME的字符转换后缀定义
 * ----------
 */
#define TM_SUFFIX_LEN	2

static const KeySuffix DCH_suff[] = {
	{"FM", 2, DCH_S_FM, SUFFTYPE_PREFIX},
	{"fm", 2, DCH_S_FM, SUFFTYPE_PREFIX},
	{"TM", TM_SUFFIX_LEN, DCH_S_TM, SUFFTYPE_PREFIX},
	{"tm", 2, DCH_S_TM, SUFFTYPE_PREFIX},
	{"TH", 2, DCH_S_TH, SUFFTYPE_POSTFIX},
	{"th", 2, DCH_S_th, SUFFTYPE_POSTFIX},
	{"SP", 2, DCH_S_SP, SUFFTYPE_POSTFIX},
	/* last */
	{NULL, 0, 0, 0}
};


/* ----------
 * 格式图片（关键字）。
 *
 * 关键字字段；按字母顺序排序，但字符串类似的排序
 *		  从复杂到简单：
 *
 *	（示例：“DDD”，“DD”，“Day”，“D”）
 *
 * （此特定排序需要算法进行字符串的顺序搜索，
 * 无确切结束；-> “HH12blabla”中的关键字是什么？ - “HH”
 * 或“HH12”？您必须首先尝试“HH12”，因为“HH”在字符串中，但
 * 这并不好。
 *
 * （！）
 *	 - 关键字的位置类似于枚举DCH/NUM_poz中的位置。
 * （！）
 *
 * 为了快速搜索，使用'int index[]'，索引是从位置
 * 32（' '）到126（~）的ascii表，在此索引中，每个ASCII位置都有DCH_ / NUM_
 * 枚举或-1（如果字符未在关键字中使用）。字符串“MM”的搜索示例：
 *	1)	查看索引到index['M' - 32]，
 *	2)	从索引中获取关键字位置（枚举DCH_MI）
 *	3)	从该位置在keywords[]中进行顺序搜索
 *
 * ----------
 */

typedef enum
{
	DCH_A_D,
	DCH_A_M,
	DCH_AD,
	DCH_AM,
	DCH_B_C,
	DCH_BC,
	DCH_CC,
	DCH_DAY,
	DCH_DDD,
	DCH_DD,
	DCH_DY,
	DCH_Day,
	DCH_Dy,
	DCH_D,
	DCH_FF1,					/* FFn代码必须是连续的 */
	DCH_FF2,
	DCH_FF3,
	DCH_FF4,
	DCH_FF5,
	DCH_FF6,
	DCH_FX,						/* 全局后缀 */
	DCH_HH24,
	DCH_HH12,
	DCH_HH,
	DCH_IDDD,
	DCH_ID,
	DCH_IW,
	DCH_IYYY,
	DCH_IYY,
	DCH_IY,
	DCH_I,
	DCH_J,
	DCH_MI,
	DCH_MM,
	DCH_MONTH,
	DCH_MON,
	DCH_MS,
	DCH_Month,
	DCH_Mon,
	DCH_OF,
	DCH_P_M,
	DCH_PM,
	DCH_Q,
	DCH_RM,
	DCH_SSSSS,
	DCH_SSSS,
	DCH_SS,
	DCH_TZH,
	DCH_TZM,
	DCH_TZ,
	DCH_US,
	DCH_WW,
	DCH_W,
	DCH_Y_YYY,
	DCH_YYYY,
	DCH_YYY,
	DCH_YY,
	DCH_Y,
	DCH_a_d,
	DCH_a_m,
	DCH_ad,
	DCH_am,
	DCH_b_c,
	DCH_bc,
	DCH_cc,
	DCH_day,
	DCH_ddd,
	DCH_dd,
	DCH_dy,
	DCH_d,
	DCH_ff1,
	DCH_ff2,
	DCH_ff3,
	DCH_ff4,
	DCH_ff5,
	DCH_ff6,
	DCH_fx,
	DCH_hh24,
	DCH_hh12,
	DCH_hh,
	DCH_iddd,
	DCH_id,
	DCH_iw,
	DCH_iyyy,
	DCH_iyy,
	DCH_iy,
	DCH_i,
	DCH_j,
	DCH_mi,
	DCH_mm,
	DCH_month,
	DCH_mon,
	DCH_ms,
	DCH_of,
	DCH_p_m,
	DCH_pm,
	DCH_q,
	DCH_rm,
	DCH_sssss,
	DCH_ssss,
	DCH_ss,
	DCH_tzh,
	DCH_tzm,
	DCH_tz,
	DCH_us,
	DCH_ww,
	DCH_w,
	DCH_y_yyy,
	DCH_yyyy,
	DCH_yyy,
	DCH_yy,
	DCH_y,

	/* last */
	_DCH_last_
}			DCH_poz;

typedef enum
{
	NUM_COMMA,
	NUM_DEC,
	NUM_0,
	NUM_9,
	NUM_B,
	NUM_C,
	NUM_D,
	NUM_E,
	NUM_FM,
	NUM_G,
	NUM_L,
	NUM_MI,
	NUM_PL,
	NUM_PR,
	NUM_RN,
	NUM_SG,
	NUM_SP,
	NUM_S,
	NUM_TH,
	NUM_V,
	NUM_b,
	NUM_c,
	NUM_d,
	NUM_e,
	NUM_fm,
	NUM_g,
	NUM_l,
	NUM_mi,
	NUM_pl,
	NUM_pr,
	NUM_rn,
	NUM_sg,
	NUM_sp,
	NUM_s,
	NUM_th,
	NUM_v,

	/* last */
	_NUM_last_
}			NUM_poz;

/* ----------
 * DATE-TIME版本的关键字
 * ----------
 */
static const KeyWord DCH_keywords[] = {
/*	名称，长度，ID，是否为数字，日期模式 */
	{"A.D.", 4, DCH_A_D, false, FROM_CHAR_DATE_NONE},	/* A */
	{"A.M.", 4, DCH_A_M, false, FROM_CHAR_DATE_NONE},
	{"AD", 2, DCH_AD, false, FROM_CHAR_DATE_NONE},
	{"AM", 2, DCH_AM, false, FROM_CHAR_DATE_NONE},
	{"B.C.", 4, DCH_B_C, false, FROM_CHAR_DATE_NONE},	/* B */
	{"BC", 2, DCH_BC, false, FROM_CHAR_DATE_NONE},
	{"CC", 2, DCH_CC, true, FROM_CHAR_DATE_NONE},	/* C */
	{"DAY", 3, DCH_DAY, false, FROM_CHAR_DATE_NONE},	/* D */
	{"DDD", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
	{"DD", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
	{"DY", 2, DCH_DY, false, FROM_CHAR_DATE_NONE},
	{"Day", 3, DCH_Day, false, FROM_CHAR_DATE_NONE},
	{"Dy", 2, DCH_Dy, false, FROM_CHAR_DATE_NONE},
	{"D", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
	{"FF1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* F */
	{"FF2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
	{"FF3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
	{"FF4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
	{"FF5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
	{"FF6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
	{"FX", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
	{"HH24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* H */
	{"HH12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
	{"HH", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
	{"IDDD", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK},	/* I */
	{"ID", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
	{"IW", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
	{"IYYY", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
	{"IYY", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
	{"IY", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
	{"I", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
	{"J", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* J */
	{"MI", 2, DCH_MI, true, FROM_CHAR_DATE_NONE},	/* M */
	{"MM", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
	{"MONTH", 5, DCH_MONTH, false, FROM_CHAR_DATE_GREGORIAN},
	{"MON", 3, DCH_MON, false, FROM_CHAR_DATE_GREGORIAN},
	{"MS", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
	{"Month", 5, DCH_Month, false, FROM_CHAR_DATE_GREGORIAN},
	{"Mon", 3, DCH_Mon, false, FROM_CHAR_DATE_GREGORIAN},
	{"OF", 2, DCH_OF, false, FROM_CHAR_DATE_NONE},	/* O */
	{"P.M.", 4, DCH_P_M, false, FROM_CHAR_DATE_NONE},	/* P */
	{"PM", 2, DCH_PM, false, FROM_CHAR_DATE_NONE},
	{"Q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* Q */
	{"RM", 2, DCH_RM, false, FROM_CHAR_DATE_GREGORIAN}, /* R */
	{"SSSSS", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* S */
	{"SSSS", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
	{"SS", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
	{"TZH", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE},	/* T */
	{"TZM", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
	{"TZ", 2, DCH_TZ, false, FROM_CHAR_DATE_NONE},
	{"US", 2, DCH_US, true, FROM_CHAR_DATE_NONE},	/* U */
	{"WW", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN},	/* W */
	{"W", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
	{"Y,YYY", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN},	/* Y */
	{"YYYY", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
	{"YYY", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
	{"YY", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
	{"Y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},
	{"a.d.", 4, DCH_a_d, false, FROM_CHAR_DATE_NONE},	/* a */
	{"a.m.", 4, DCH_a_m, false, FROM_CHAR_DATE_NONE},
	{"ad", 2, DCH_ad, false, FROM_CHAR_DATE_NONE},
	{"am", 2, DCH_am, false, FROM_CHAR_DATE_NONE},
	{"b.c.", 4, DCH_b_c, false, FROM_CHAR_DATE_NONE},	/* b */
	{"bc", 2, DCH_bc, false, FROM_CHAR_DATE_NONE},
	{"cc", 2, DCH_CC, true, FROM_CHAR_DATE_NONE},	/* c */
	{"day", 3, DCH_day, false, FROM_CHAR_DATE_NONE},	/* d */
	{"ddd", 3, DCH_DDD, true, FROM_CHAR_DATE_GREGORIAN},
	{"dd", 2, DCH_DD, true, FROM_CHAR_DATE_GREGORIAN},
	{"dy", 2, DCH_dy, false, FROM_CHAR_DATE_NONE},
	{"d", 1, DCH_D, true, FROM_CHAR_DATE_GREGORIAN},
	{"ff1", 3, DCH_FF1, true, FROM_CHAR_DATE_NONE}, /* f */
	{"ff2", 3, DCH_FF2, true, FROM_CHAR_DATE_NONE},
	{"ff3", 3, DCH_FF3, true, FROM_CHAR_DATE_NONE},
	{"ff4", 3, DCH_FF4, true, FROM_CHAR_DATE_NONE},
	{"ff5", 3, DCH_FF5, true, FROM_CHAR_DATE_NONE},
	{"ff6", 3, DCH_FF6, true, FROM_CHAR_DATE_NONE},
	{"fx", 2, DCH_FX, false, FROM_CHAR_DATE_NONE},
	{"hh24", 4, DCH_HH24, true, FROM_CHAR_DATE_NONE},	/* h */
	{"hh12", 4, DCH_HH12, true, FROM_CHAR_DATE_NONE},
	{"hh", 2, DCH_HH, true, FROM_CHAR_DATE_NONE},
	{"iddd", 4, DCH_IDDD, true, FROM_CHAR_DATE_ISOWEEK},	/* i */
	{"id", 2, DCH_ID, true, FROM_CHAR_DATE_ISOWEEK},
	{"iw", 2, DCH_IW, true, FROM_CHAR_DATE_ISOWEEK},
	{"iyyy", 4, DCH_IYYY, true, FROM_CHAR_DATE_ISOWEEK},
	{"iyy", 3, DCH_IYY, true, FROM_CHAR_DATE_ISOWEEK},
	{"iy", 2, DCH_IY, true, FROM_CHAR_DATE_ISOWEEK},
	{"i", 1, DCH_I, true, FROM_CHAR_DATE_ISOWEEK},
	{"j", 1, DCH_J, true, FROM_CHAR_DATE_NONE}, /* j */
	{"mi", 2, DCH_MI, true, FROM_CHAR_DATE_NONE},	/* m */
	{"mm", 2, DCH_MM, true, FROM_CHAR_DATE_GREGORIAN},
	{"month", 5, DCH_month, false, FROM_CHAR_DATE_GREGORIAN},
	{"mon", 3, DCH_mon, false, FROM_CHAR_DATE_GREGORIAN},
	{"ms", 2, DCH_MS, true, FROM_CHAR_DATE_NONE},
	{"of", 2, DCH_OF, false, FROM_CHAR_DATE_NONE},	/* o */
	{"p.m.", 4, DCH_p_m, false, FROM_CHAR_DATE_NONE},	/* p */
	{"pm", 2, DCH_pm, false, FROM_CHAR_DATE_NONE},
	{"q", 1, DCH_Q, true, FROM_CHAR_DATE_NONE}, /* q */
	{"rm", 2, DCH_rm, false, FROM_CHAR_DATE_GREGORIAN}, /* r */
	{"sssss", 5, DCH_SSSS, true, FROM_CHAR_DATE_NONE},	/* s */
	{"ssss", 4, DCH_SSSS, true, FROM_CHAR_DATE_NONE},
	{"ss", 2, DCH_SS, true, FROM_CHAR_DATE_NONE},
	{"tzh", 3, DCH_TZH, false, FROM_CHAR_DATE_NONE},	/* t */
	{"tzm", 3, DCH_TZM, true, FROM_CHAR_DATE_NONE},
	{"tz", 2, DCH_tz, false, FROM_CHAR_DATE_NONE},
	{"us", 2, DCH_US, true, FROM_CHAR_DATE_NONE},	/* u */
	{"ww", 2, DCH_WW, true, FROM_CHAR_DATE_GREGORIAN},	/* w */
	{"w", 1, DCH_W, true, FROM_CHAR_DATE_GREGORIAN},
	{"y,yyy", 5, DCH_Y_YYY, true, FROM_CHAR_DATE_GREGORIAN},	/* y */
	{"yyyy", 4, DCH_YYYY, true, FROM_CHAR_DATE_GREGORIAN},
	{"yyy", 3, DCH_YYY, true, FROM_CHAR_DATE_GREGORIAN},
	{"yy", 2, DCH_YY, true, FROM_CHAR_DATE_GREGORIAN},
	{"y", 1, DCH_Y, true, FROM_CHAR_DATE_GREGORIAN},

	/* last */
	{NULL, 0, 0, 0, 0}
};

/* ----------
 * NUMBER版本的关键字
 *
 * is_digit和date_mode字段在此不相关。
 * ----------
 */
static const KeyWord NUM_keywords[] = {
/*	名称，长度，ID			在索引中 */
	{",", 1, NUM_COMMA},		/* , */
	{".", 1, NUM_DEC},			/* . */
	{"0", 1, NUM_0},			/* 0 */
	{"9", 1, NUM_9},			/* 9 */
	{"B", 1, NUM_B},			/* B */
	{"C", 1, NUM_C},			/* C */
	{"D", 1, NUM_D},			/* D */
	{"EEEE", 4, NUM_E},			/* E */
	{"FM", 2, NUM_FM},			/* F */
	{"G", 1, NUM_G},			/* G */
	{"L", 1, NUM_L},			/* L */
	{"MI", 2, NUM_MI},			/* M */
	{"PL", 2, NUM_PL},			/* P */
	{"PR", 2, NUM_PR},
	{"RN", 2, NUM_RN},			/* R */
	{"SG", 2, NUM_SG},			/* S */
	{"SP", 2, NUM_SP},
	{"S", 1, NUM_S},
	{"TH", 2, NUM_TH},			/* T */
	{"V", 1, NUM_V},			/* V */
	{"b", 1, NUM_B},			/* b */
	{"c", 1, NUM_C},			/* c */
	{"d", 1, NUM_D},			/* d */
	{"eeee", 4, NUM_E},			/* e */
	{"fm", 2, NUM_FM},			/* f */
	{"g", 1, NUM_G},			/* g */
	{"l", 1, NUM_L},			/* l */
	{"mi", 2, NUM_MI},			/* m */
	{"pl", 2, NUM_PL},			/* p */
	{"pr", 2, NUM_PR},
	{"rn", 2, NUM_rn},			/* r */
	{"sg", 2, NUM_SG},			/* s */
	{"sp", 2, NUM_SP},
	{"s", 1, NUM_S},
	{"th", 2, NUM_th},			/* t */
	{"v", 1, NUM_V},			/* v */

	/* last */
	{NULL, 0, 0}
};


/* ----------
 * DATE-TIME版本的关键字索引
 * ----------
 */
static const int DCH_index[KeyWord_INDEX_SIZE] = {
/*
0	1	2	3	4	5	6	7	8	9
*/
	/*---- 前0..31个字符被跳过 ----*/

	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, -1, DCH_A_D, DCH_B_C, DCH_CC, DCH_DAY, -1,
	DCH_FF1, -1, DCH_HH24, DCH_IDDD, DCH_J, -1, -1, DCH_MI, -1, DCH_OF,
	DCH_P_M, DCH_Q, DCH_RM, DCH_SSSSS, DCH_TZH, DCH_US, -1, DCH_WW, -1, DCH_Y_YYY,
	-1, -1, -1, -1, -1, -1, -1, DCH_a_d, DCH_b_c, DCH_cc,
	DCH_day, -1, DCH_ff1, -1, DCH_hh24, DCH_iddd, DCH_j, -1, -1, DCH_mi,
	-1, DCH_of, DCH_p_m, DCH_q, DCH_rm, DCH_sssss, DCH_tzh, DCH_us, -1, DCH_ww,
	-1, DCH_y_yyy, -1, -1, -1, -1

	/*---- 超过126的字符被跳过 ----*/
};

/* ----------
 * NUMBER版本的关键字索引
 * ----------
 */
static const int NUM_index[KeyWord_INDEX_SIZE] = {
/*
0	1	2	3	4	5	6	7	8	9
*/
	/*---- 前0..31个字符被跳过 ----*/

	-1, -1, -1, -1, -1, -1, -1, -1,
	-1, -1, -1, -1, NUM_COMMA, -1, NUM_DEC, -1, NUM_0, -1,
	-1, -1, -1, -1, -1, -1, -1, NUM_9, -1, -1,
	-1, -1, -1, -1, -1, -1, NUM_B, NUM_C, NUM_D, NUM_E,
	NUM_FM, NUM_G, -1, -1, -1, -1, NUM_L, NUM_MI, -1, -1,
	NUM_PL, -1, NUM_RN, NUM_SG, NUM_TH, -1, NUM_V, -1, -1, -1,
	-1, -1, -1, -1, -1, -1, -1, -1, NUM_b, NUM_c,
	NUM_d, NUM_e, NUM_fm, NUM_g, -1, -1, -1, -1, NUM_l, NUM_mi,
	-1, -1, NUM_pl, -1, NUM_rn, NUM_sg, NUM_th, -1, NUM_v, -1,
	-1, -1, -1, -1, -1, -1

	/*---- 超过126的字符被跳过 ----*/
};

/* ----------
 * 数字处理结构
 * ----------
 */
typedef struct NUMProc
{
	bool		is_to_char;
	NUMDesc    *Num;			/* 数字描述		*/

	int			sign,			/* '-'或'+'			*/
				sign_wrote,		/* 已写符号		*/
				num_count,		/* 写入数字的位数	*/
				num_in,			/* 在数字中		*/
				num_curr,		/* 当前数字中的位置	*/
				out_pre_spaces, /* 第一个数字前的空格	*/

				read_dec,		/* to_number - 已读取的小数点	*/
				read_post,		/* to_number - 小数位数 */
				read_pre;		/* to_number - 非小数位数 */

	char	   *number,			/* 包含数字的字符串	*/
			   *number_p,		/* 当前数字位置的指针 */
			   *inout,			/* in / out buffer	*/
			   *inout_p,		/* 当前输入输出位置的指针 */
			   *last_relevant,	/* 小数点后的最后一个相关数字 */

			   *L_negative_sign,	/* Locale */
			   *L_positive_sign,
			   *decimal,
			   *L_thousands_sep,
			   *L_currency_symbol;
} NUMProc;

/* DCH_from_char() 的返回标志 */
#define DCH_DATED	0x01
#define DCH_TIMED	0x02
#define DCH_ZONED	0x04

/* ----------
 * 函数
 * ----------
 */
static const KeyWord *fc_index_seq_search(const char *fc_str, const KeyWord *fc_kw,
									   const int *fc_index);
static const KeySuffix *fc_suff_search(const char *fc_str, const KeySuffix *fc_suf, int fc_type);
static bool fc_is_separator_char(const char *fc_str);
static void fc_NUMDesc_prepare(NUMDesc *fc_num, FormatNode *fc_n);
static void fc_parse_format(FormatNode *fc_node, const char *fc_str, const KeyWord *fc_kw,
						 const KeySuffix *fc_suf, const int *fc_index, uint32 fc_flags, NUMDesc *fc_Num);

static void fc_DCH_to_char(FormatNode *fc_node, bool fc_is_interval,
						TmToChar *fc_in, char *fc_out, Oid fc_collid);
static void fc_DCH_from_char(FormatNode *fc_node, const char *fc_in, TmFromChar *fc_out,
						  Oid fc_collid, bool fc_std, bool *fc_have_error);

#ifdef DEBUG_TO_FROM_CHAR
static void fc_dump_index(const KeyWord *fc_k, const int *fc_index);
static void fc_dump_node(FormatNode *fc_node, int fc_max);
#endif

static const char *get_th(char *fc_num, int fc_type);
static char *fc_str_numth(char *fc_dest, char *fc_num, int fc_type);
static int	fc_adjust_partial_year_to_2020(int fc_year);
static int	fc_strspace_len(const char *fc_str);
static void fc_from_char_set_mode(TmFromChar *fc_tmfc, const FromCharDateMode fc_mode,
							   bool *fc_have_error);
static void fc_from_char_set_int(int *fc_dest, const int fc_value, const FormatNode *fc_node,
							  bool *fc_have_error);
static int	fc_from_char_parse_int_len(int *fc_dest, const char **fc_src, const int fc_len,
									FormatNode *fc_node, bool *fc_have_error);
static int	fc_from_char_parse_int(int *fc_dest, const char **fc_src, FormatNode *fc_node,
								bool *fc_have_error);
static int	fc_seq_search_ascii(const char *fc_name, const char *const *fc_array, int *fc_len);
static int	fc_seq_search_localized(const char *fc_name, char **fc_array, int *fc_len,
								 Oid fc_collid);
static int	fc_from_char_seq_search(int *fc_dest, const char **fc_src,
								 const char *const *fc_array,
								 char **fc_localized_array, Oid fc_collid,
								 FormatNode *fc_node, bool *fc_have_error);
static void fc_do_to_timestamp(text *fc_date_txt, text *fc_fmt, Oid fc_collid, bool fc_std,
							struct pg_tm *fc_tm, fsec_t *fc_fsec, int *fc_fprec,
							uint32 *fc_flags, bool *fc_have_error);
static char *fc_fill_str(char *fc_str, int fc_c, int fc_max);
static FormatNode *fc_NUM_cache(int fc_len, NUMDesc *fc_Num, text *fc_pars_str, bool *fc_shouldFree);
static char *fc_int_to_roman(int fc_number);
static void fc_NUM_prepare_locale(NUMProc *fc_Np);
static char *get_last_relevant_decnum(char *fc_num);
static void fc_NUM_numpart_from_char(NUMProc *fc_Np, int fc_id, int fc_input_len);
static void fc_NUM_numpart_to_char(NUMProc *fc_Np, int fc_id);
static char *fc_NUM_processor(FormatNode *fc_node, NUMDesc *fc_Num, char *fc_inout,
						   char *fc_number, int fc_input_len, int fc_to_char_out_pre_spaces,
						   int fc_sign, bool fc_is_to_char, Oid fc_collid);
static DCHCacheEntry *fc_DCH_cache_getnew(const char *fc_str, bool fc_std);
static DCHCacheEntry *fc_DCH_cache_search(const char *fc_str, bool fc_std);
static DCHCacheEntry *fc_DCH_cache_fetch(const char *fc_str, bool fc_std);
static NUMCacheEntry *fc_NUM_cache_getnew(const char *fc_str);
static NUMCacheEntry *fc_NUM_cache_search(const char *fc_str);
static NUMCacheEntry *fc_NUM_cache_fetch(const char *fc_str);


/* ----------
 * 快速顺序搜索，使用索引进行数据选择
 * 进入顺序循环（对于不需要的字符串非常快）
 * （在格式解析中不能使用二进制搜索）
 * ----------
 */
static const KeyWord *
fc_index_seq_search(const char *fc_str, const KeyWord *fc_kw, const int *fc_index)
{
	int			fc_poz;

	if (!KeyWord_INDEX_FILTER(*fc_str))
		return NULL;

	if ((fc_poz = *(fc_index + (*fc_str - ' '))) > -1)
	{
		const KeyWord *fc_k = fc_kw + fc_poz;

		do
		{
			if (strncmp(fc_str, fc_k->name, fc_k->len) == 0)
				return fc_k;
			fc_k++;
			if (!fc_k->name)
				return NULL;
		} while (*fc_str == *fc_k->name);
	}
	return NULL;
}

static const KeySuffix *
fc_suff_search(const char *fc_str, const KeySuffix *fc_suf, int fc_type)
{
	const KeySuffix *fc_s;

	for (fc_s = fc_suf; fc_s->name != NULL; fc_s++)
	{
		if (fc_s->type != fc_type)
			continue;

		if (strncmp(fc_str, fc_s->name, fc_s->len) == 0)
			return fc_s;
	}
	return NULL;
}

static bool fc_is_separator_char(const char *fc_str)
{
	/* ASCII 可打印字符，但不是字母或数字 */
	return (*fc_str > 0x20 && *fc_str < 0x7F &&
			!(*fc_str >= 'A' && *fc_str <= 'Z') &&
			!(*fc_str >= 'a' && *fc_str <= 'z') &&
			!(*fc_str >= '0' && *fc_str <= '9'));
}

/* ----------
 * 通过 FormatNode 结构准备 NUMDesc（数字描述结构）
 * ----------
 */
static void fc_NUMDesc_prepare(NUMDesc *fc_num, FormatNode *fc_n)
{
	if (fc_n->type != NODE_TYPE_ACTION)
		return;

	if (IS_EEEE(fc_num) && fc_n->key->id != NUM_E)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("\"EEEE\" must be the last pattern used")));

	switch (fc_n->key->id)
	{
		case NUM_9:
			if (IS_BRACKET(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("\"9\" must be ahead of \"PR\"")));
			if (IS_MULTI(fc_num))
			{
				++fc_num->multi;
				break;
			}
			if (IS_DECIMAL(fc_num))
				++fc_num->post;
			else
				++fc_num->pre;
			break;

		case NUM_0:
			if (IS_BRACKET(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("\"0\" must be ahead of \"PR\"")));
			if (!IS_ZERO(fc_num) && !IS_DECIMAL(fc_num))
			{
				fc_num->flag |= NUM_F_ZERO;
				fc_num->zero_start = fc_num->pre + 1;
			}
			if (!IS_DECIMAL(fc_num))
				++fc_num->pre;
			else
				++fc_num->post;

			fc_num->zero_end = fc_num->pre + fc_num->post;
			break;

		case NUM_B:
			if (fc_num->pre == 0 && fc_num->post == 0 && (!IS_ZERO(fc_num)))
				fc_num->flag |= NUM_F_BLANK;
			break;

		case NUM_D:
			fc_num->flag |= NUM_F_LDECIMAL;
			fc_num->need_locale = true;
			/* FALLTHROUGH */
		case NUM_DEC:
			if (IS_DECIMAL(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("multiple decimal points")));
			if (IS_MULTI(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"V\" and decimal point together")));
			fc_num->flag |= NUM_F_DECIMAL;
			break;

		case NUM_FM:
			fc_num->flag |= NUM_F_FILLMODE;
			break;

		case NUM_S:
			if (IS_LSIGN(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"S\" twice")));
			if (IS_PLUS(fc_num) || IS_MINUS(fc_num) || IS_BRACKET(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"S\" and \"PL\"/\"MI\"/\"SG\"/\"PR\" together")));
			if (!IS_DECIMAL(fc_num))
			{
				fc_num->lsign = NUM_LSIGN_PRE;
				fc_num->pre_lsign_num = fc_num->pre;
				fc_num->need_locale = true;
				fc_num->flag |= NUM_F_LSIGN;
			}
			else if (fc_num->lsign == NUM_LSIGN_NONE)
			{
				fc_num->lsign = NUM_LSIGN_POST;
				fc_num->need_locale = true;
				fc_num->flag |= NUM_F_LSIGN;
			}
			break;

		case NUM_MI:
			if (IS_LSIGN(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"S\" and \"MI\" together")));
			fc_num->flag |= NUM_F_MINUS;
			if (IS_DECIMAL(fc_num))
				fc_num->flag |= NUM_F_MINUS_POST;
			break;

		case NUM_PL:
			if (IS_LSIGN(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"S\" and \"PL\" together")));
			fc_num->flag |= NUM_F_PLUS;
			if (IS_DECIMAL(fc_num))
				fc_num->flag |= NUM_F_PLUS_POST;
			break;

		case NUM_SG:
			if (IS_LSIGN(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"S\" and \"SG\" together")));
			fc_num->flag |= NUM_F_MINUS;
			fc_num->flag |= NUM_F_PLUS;
			break;

		case NUM_PR:
			if (IS_LSIGN(fc_num) || IS_PLUS(fc_num) || IS_MINUS(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"PR\" and \"S\"/\"PL\"/\"MI\"/\"SG\" together")));
			fc_num->flag |= NUM_F_BRACKET;
			break;

		case NUM_rn:
		case NUM_RN:
			fc_num->flag |= NUM_F_ROMAN;
			break;

		case NUM_L:
		case NUM_G:
			fc_num->need_locale = true;
			break;

		case NUM_V:
			if (IS_DECIMAL(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"V\" and decimal point together")));
			fc_num->flag |= NUM_F_MULTI;
			break;

		case NUM_E:
			if (IS_EEEE(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("cannot use \"EEEE\" twice")));
			if (IS_BLANK(fc_num) || IS_FILLMODE(fc_num) || IS_LSIGN(fc_num) ||
				IS_BRACKET(fc_num) || IS_MINUS(fc_num) || IS_PLUS(fc_num) ||
				IS_ROMAN(fc_num) || IS_MULTI(fc_num))
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("\"EEEE\" is incompatible with other formats"),
						 errdetail("\"EEEE\" may only be used together with digit and decimal point patterns.")));
			fc_num->flag |= NUM_F_EEEE;
			break;
	}
}

/* ----------
 * 格式解析器，搜索小关键字和关键字后缀，并构建
 * 格式节点树。
 *
 * 针对日期-时间和数字版本
 * ----------
 */
static void fc_parse_format(FormatNode *fc_node, const char *fc_str, const KeyWord *fc_kw,
			 const KeySuffix *fc_suf, const int *fc_index, uint32 fc_flags, NUMDesc *fc_Num)
{
	FormatNode *fc_n;

#ifdef DEBUG_TO_FROM_CHAR
	elog(DEBUG_elog_output, "to_char/number(): run parser");
#endif

	fc_n = fc_node;

	while (*fc_str)
	{
		int			fc_suffix = 0;
		const KeySuffix *fc_s;

		/*
		 * 前缀
		 */
		if ((fc_flags & DCH_FLAG) &&
			(fc_s = fc_suff_search(fc_str, fc_suf, SUFFTYPE_PREFIX)) != NULL)
		{
			fc_suffix |= fc_s->id;
			if (fc_s->len)
				fc_str += fc_s->len;
		}

		/*
		 * 关键字
		 */
		if (*fc_str && (fc_n->key = fc_index_seq_search(fc_str, fc_kw, fc_index)) != NULL)
		{
			fc_n->type = NODE_TYPE_ACTION;
			fc_n->suffix = fc_suffix;
			if (fc_n->key->len)
				fc_str += fc_n->key->len;

			/*
			 * NUM 版本：准备全局 NUMDesc 结构
			 */
			if (fc_flags & NUM_FLAG)
				fc_NUMDesc_prepare(fc_Num, fc_n);

			/*
			 * 后缀
			 */
			if ((fc_flags & DCH_FLAG) && *fc_str &&
				(fc_s = fc_suff_search(fc_str, fc_suf, SUFFTYPE_POSTFIX)) != NULL)
			{
				fc_n->suffix |= fc_s->id;
				if (fc_s->len)
					fc_str += fc_s->len;
			}

			fc_n++;
		}
		else if (*fc_str)
		{
			int			fc_chlen;

			if ((fc_flags & STD_FLAG) && *fc_str != '"')
			{
				/*
				 * 标准模式，只允许以下分隔符："-./,':; "。
				 * 然而，即使在标准模式下，我们也支持双引号
				 * （见下文）。这是我们对标准模式的扩展。
				 */
				if (strchr("-./,':; ", *fc_str) == NULL)
					ereport(ERROR,
							(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
							 errmsg("invalid datetime format separator: \"%s\"",
									pnstrdup(fc_str, pg_mblen(fc_str)))));

				if (*fc_str == ' ')
					fc_n->type = NODE_TYPE_SPACE;
				else
					fc_n->type = NODE_TYPE_SEPARATOR;

				fc_n->character[0] = *fc_str;
				fc_n->character[1] = '\0';
				fc_n->key = NULL;
				fc_n->suffix = 0;
				fc_n++;
				fc_str++;
			}
			else if (*fc_str == '"')
			{
				/*
				 * 处理双引号的文字字符串（如果有）
				 */
				fc_str++;
				while (*fc_str)
				{
					if (*fc_str == '"')
					{
						fc_str++;
						break;
					}
					/* 反斜杠转义下一个字符（如果有） */
					if (*fc_str == '\\' && *(fc_str + 1))
						fc_str++;
					fc_chlen = pg_mblen(fc_str);
					fc_n->type = NODE_TYPE_CHAR;
					memcpy(fc_n->character, fc_str, fc_chlen);
					fc_n->character[fc_chlen] = '\0';
					fc_n->key = NULL;
					fc_n->suffix = 0;
					fc_n++;
					fc_str += fc_chlen;
				}
			}
			else
			{
				/*
				 * 在双引号字符串外，反斜杠只有在紧接着双引号之前时才特殊。
				 */
				if (*fc_str == '\\' && *(fc_str + 1) == '"')
					fc_str++;
				fc_chlen = pg_mblen(fc_str);

				if ((fc_flags & DCH_FLAG) && fc_is_separator_char(fc_str))
					fc_n->type = NODE_TYPE_SEPARATOR;
				else if (isspace((unsigned char) *fc_str))
					fc_n->type = NODE_TYPE_SPACE;
				else
					fc_n->type = NODE_TYPE_CHAR;

				memcpy(fc_n->character, fc_str, fc_chlen);
				fc_n->character[fc_chlen] = '\0';
				fc_n->key = NULL;
				fc_n->suffix = 0;
				fc_n++;
				fc_str += fc_chlen;
			}
		}
	}

	fc_n->type = NODE_TYPE_END;
	fc_n->suffix = 0;
}

/* ----------
 * 调试：转储 FormatNode 树（调试）
 * ----------
 */
#ifdef DEBUG_TO_FROM_CHAR

#define DUMP_THth(_suf) (S_TH(_suf) ? "TH" : (S_th(_suf) ? "th" : " "))
#define DUMP_FM(_suf)	(S_FM(_suf) ? "FM" : " ")

static void fc_dump_node(FormatNode *fc_node, int fc_max)
{
	FormatNode *fc_n;
	int			fc_a;

	elog(DEBUG_elog_output, "to_from-char(): DUMP FORMAT");

	for (fc_a = 0, fc_n = fc_node; fc_a <= fc_max; fc_n++, fc_a++)
	{
		if (fc_n->type == NODE_TYPE_ACTION)
			elog(DEBUG_elog_output, "%d:\t NODE_TYPE_ACTION '%s'\t(%s,%s)",
				 fc_a, fc_n->key->name, DUMP_THth(fc_n->suffix), DUMP_FM(fc_n->suffix));
		else if (fc_n->type == NODE_TYPE_CHAR)
			elog(DEBUG_elog_output, "%d:\t NODE_TYPE_CHAR '%s'",
				 fc_a, fc_n->character);
		else if (fc_n->type == NODE_TYPE_END)
		{
			elog(DEBUG_elog_output, "%d:\t NODE_TYPE_END", fc_a);
			return;
		}
		else
			elog(DEBUG_elog_output, "%d:\t unknown NODE!", fc_a);
	}
}
#endif							/* DEBUG */

/*****************************************************************************
 *			私有工具
 *****************************************************************************/

/* ----------
 * 为简单（1..9）数字返回 ST/ND/RD/TH
 * 类型 --> 0 大写，1 小写
 * ----------
 */
static const char * get_th(char *fc_num, int fc_type)
{
	int			fc_len = strlen(fc_num),
				fc_last;

	fc_last = *(fc_num + (fc_len - 1));
	if (!isdigit((unsigned char) fc_last))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
				 errmsg("\"%s\" is not a number", fc_num)));

	/*
	 * 所有“十几”（<x>1[0-9]）都会得到 'TH/th'，而 <x>[02-9][123] 仍然会得到
	 * 'ST/st'、'ND/nd'、'RD/rd'，分别
	 */
	if ((fc_len > 1) && (fc_num[fc_len - 2] == '1'))
		fc_last = 0;

	switch (fc_last)
	{
		case '1':
			if (fc_type == TH_UPPER)
				return numTH[0];
			return numth[0];
		case '2':
			if (fc_type == TH_UPPER)
				return numTH[1];
			return numth[1];
		case '3':
			if (fc_type == TH_UPPER)
				return numTH[2];
			return numth[2];
		default:
			if (fc_type == TH_UPPER)
				return numTH[3];
			return numth[3];
	}
}

/* ----------
 * 将字符串数字转换为序数字符串数字
 * 类型 --> 0 大写，1 小写
 * ----------
 */
static char * fc_str_numth(char *fc_dest, char *fc_num, int fc_type)
{
	if (fc_dest != fc_num)
		strcpy(fc_dest, fc_num);
	strcat(fc_dest, get_th(fc_num, fc_type));
	return fc_dest;
}

/*****************************************************************************
 *			大写/小写/首字母大写函数
 *****************************************************************************/

#ifdef USE_ICU

typedef int32_t (*ICU_Convert_Func) (UChar *fc_dest, int32_t fc_destCapacity,
									 const UChar *fc_src, int32_t fc_srcLength,
									 const char *fc_locale,
									 UErrorCode *fc_pErrorCode);

static int32_t
fc_icu_convert_case(ICU_Convert_Func fc_func, pg_locale_t fc_mylocale,
				 UChar **fc_buff_dest, UChar *fc_buff_source, int32_t fc_len_source)
{
	UErrorCode	fc_status;
	int32_t		fc_len_dest;

	fc_len_dest = fc_len_source;		/* 首先尝试相同长度 */
	*fc_buff_dest = palloc(fc_len_dest * sizeof(**fc_buff_dest));
	fc_status = U_ZERO_ERROR;
	fc_len_dest = fc_func(*fc_buff_dest, fc_len_dest, fc_buff_source, fc_len_source,
					fc_mylocale->info.icu.locale, &fc_status);
	if (fc_status == U_BUFFER_OVERFLOW_ERROR)
	{
		/* 再次尝试调整长度 */
		pfree(*fc_buff_dest);
		*fc_buff_dest = palloc(fc_len_dest * sizeof(**fc_buff_dest));
		fc_status = U_ZERO_ERROR;
		fc_len_dest = fc_func(*fc_buff_dest, fc_len_dest, fc_buff_source, fc_len_source,
						fc_mylocale->info.icu.locale, &fc_status);
	}
	if (U_FAILURE(fc_status))
		ereport(ERROR,
				(errmsg("case conversion failed: %s", u_errorName(fc_status))));
	return fc_len_dest;
}

static int32_t
fc_u_strToTitle_default_BI(UChar *fc_dest, int32_t fc_destCapacity,
						const UChar *fc_src, int32_t fc_srcLength,
						const char *fc_locale,
						UErrorCode *fc_pErrorCode)
{
	return u_strToTitle(fc_dest, fc_destCapacity, fc_src, fc_srcLength,
						NULL, fc_locale, fc_pErrorCode);
}

#endif							/* USE_ICU */

/*
 * 如果系统提供了所需的宽字符操作函数
 * （这些都是 C99 标准化的），那么我们将根据需要实现大写/小写/首字母大写
 * 的功能。否则，我们使用传统的 <ctype.h> 函数，这当然无法在多字节字符集中按预期工作。
 * 请注意，在这两种情况下，我们实际上都假设数据库字符编码与
 * LC_CTYPE 所暗示的编码相匹配。
 *
 * 如果系统提供 locale_t 和相关函数（这些由 Open Group 的 XBD 标准化），
 * 我们可以支持既不是默认也不是 C 的排序。
 * 代码编写成可以处理具有宽字符和 locale_t 的组合，
 * 尽管平台 unlikely 同时没有前者和后者。
 */

/*
 * 考虑排序的，宽字符敏感的小写函数
 *
 * 我们传递字节数，以便可以将 varlena 和 char* 传递给此函数。
 * 结果是一个 palloc'd，空终止字符串。
 */
char * str_tolower(const char *fc_buff, size_t fc_nbytes, Oid fc_collid)
{
	char	   *fc_result;

	if (!fc_buff)
		return NULL;

	if (!OidIsValid(fc_collid))
	{
		/*
		 * 这通常意味着解析器无法解决隐式排序的冲突，
		 * 因此以这种方式报告。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INDETERMINATE_COLLATION),
				 errmsg("could not determine which collation to use for %s function",
						"lower()"),
				 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}

	/* C/POSIX比较无论数据库编码如何都使用此路径 */
	if (lc_ctype_is_c(fc_collid))
	{
		fc_result = asc_tolower(fc_buff, fc_nbytes);
	}
	else
	{
		pg_locale_t fc_mylocale;

		fc_mylocale = pg_newlocale_from_collation(fc_collid);

#ifdef USE_ICU
		if (fc_mylocale && fc_mylocale->provider == COLLPROVIDER_ICU)
		{
			int32_t		fc_len_uchar;
			int32_t		fc_len_conv;
			UChar	   *fc_buff_uchar;
			UChar	   *fc_buff_conv;

			fc_len_uchar = icu_to_uchar(&fc_buff_uchar, fc_buff, fc_nbytes);
			fc_len_conv = fc_icu_convert_case(u_strToLower, fc_mylocale,
										&fc_buff_conv, fc_buff_uchar, fc_len_uchar);
			icu_from_uchar(&fc_result, fc_buff_conv, fc_len_conv);
			pfree(fc_buff_uchar);
			pfree(fc_buff_conv);
		}
		else
#endif
		{
			if (pg_database_encoding_max_length() > 1)
			{
				wchar_t    *fc_workspace;
				size_t		fc_curr_char;
				size_t		fc_result_size;

				/* 溢出担忧 */
				if ((fc_nbytes + 1) > (INT_MAX / sizeof(wchar_t)))
					ereport(ERROR,
							(errcode(ERRCODE_OUT_OF_MEMORY),
							 errmsg("out of memory")));

				/* 输出工作区不能拥有比输入字节更多的代码 */
				fc_workspace = (wchar_t *) palloc((fc_nbytes + 1) * sizeof(wchar_t));

				char2wchar(fc_workspace, fc_nbytes + 1, fc_buff, fc_nbytes, fc_mylocale);

				for (fc_curr_char = 0; fc_workspace[fc_curr_char] != 0; fc_curr_char++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
						fc_workspace[fc_curr_char] = towlower_l(fc_workspace[fc_curr_char], fc_mylocale->info.lt);
					else
#endif
						fc_workspace[fc_curr_char] = towlower(fc_workspace[fc_curr_char]);
				}

				/*
				 * 使结果足够大；大小写变化可能会改变字节数
				 */
				fc_result_size = fc_curr_char * pg_database_encoding_max_length() + 1;
				fc_result = palloc(fc_result_size);

				wchar2char(fc_result, fc_workspace, fc_result_size, fc_mylocale);
				pfree(fc_workspace);
			}
			else
			{
				char	   *fc_p;

				fc_result = pnstrdup(fc_buff, fc_nbytes);

				/*
				 * 注意：我们假设 tolower_l() 不会坏到需要 isupper_l() 的保护测试。当使用默认
				 * 排序时，我们应用传统的 Postgres 行为，强制 ASCII 风格处理 I/i，但在非默认
				 * 排序中，你会得到排序所指定的内容。
				 */
				for (fc_p = fc_result; *fc_p; fc_p++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
						*fc_p = tolower_l((unsigned char) *fc_p, fc_mylocale->info.lt);
					else
#endif
						*fc_p = pg_tolower((unsigned char) *fc_p);
				}
			}
		}
	}

	return fc_result;
}

/*
 * 具有排序感知和宽字符感知的 upper 函数
 *
 * 我们传递字节数，以便能够将 varlena 和 char* 传递给此函数。结果是一个 palloc 分配的、以 null 结尾的字符串。
 */
char * str_toupper(const char *fc_buff, size_t fc_nbytes, Oid fc_collid)
{
	char	   *fc_result;

	if (!fc_buff)
		return NULL;

	if (!OidIsValid(fc_collid))
	{
		/*
		 * 这通常意味着解析器无法解决隐式排序的冲突，
		 * 因此以这种方式报告。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INDETERMINATE_COLLATION),
				 errmsg("could not determine which collation to use for %s function",
						"upper()"),
				 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}

	/* C/POSIX比较无论数据库编码如何都使用此路径 */
	if (lc_ctype_is_c(fc_collid))
	{
		fc_result = asc_toupper(fc_buff, fc_nbytes);
	}
	else
	{
		pg_locale_t fc_mylocale;

		fc_mylocale = pg_newlocale_from_collation(fc_collid);

#ifdef USE_ICU
		if (fc_mylocale && fc_mylocale->provider == COLLPROVIDER_ICU)
		{
			int32_t		fc_len_uchar,
						fc_len_conv;
			UChar	   *fc_buff_uchar;
			UChar	   *fc_buff_conv;

			fc_len_uchar = icu_to_uchar(&fc_buff_uchar, fc_buff, fc_nbytes);
			fc_len_conv = fc_icu_convert_case(u_strToUpper, fc_mylocale,
										&fc_buff_conv, fc_buff_uchar, fc_len_uchar);
			icu_from_uchar(&fc_result, fc_buff_conv, fc_len_conv);
			pfree(fc_buff_uchar);
			pfree(fc_buff_conv);
		}
		else
#endif
		{
			if (pg_database_encoding_max_length() > 1)
			{
				wchar_t    *fc_workspace;
				size_t		fc_curr_char;
				size_t		fc_result_size;

				/* 溢出担忧 */
				if ((fc_nbytes + 1) > (INT_MAX / sizeof(wchar_t)))
					ereport(ERROR,
							(errcode(ERRCODE_OUT_OF_MEMORY),
							 errmsg("out of memory")));

				/* 输出工作区不能拥有比输入字节更多的代码 */
				fc_workspace = (wchar_t *) palloc((fc_nbytes + 1) * sizeof(wchar_t));

				char2wchar(fc_workspace, fc_nbytes + 1, fc_buff, fc_nbytes, fc_mylocale);

				for (fc_curr_char = 0; fc_workspace[fc_curr_char] != 0; fc_curr_char++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
						fc_workspace[fc_curr_char] = towupper_l(fc_workspace[fc_curr_char], fc_mylocale->info.lt);
					else
#endif
						fc_workspace[fc_curr_char] = towupper(fc_workspace[fc_curr_char]);
				}

				/*
				 * 使结果足够大；大小写变化可能会改变字节数
				 */
				fc_result_size = fc_curr_char * pg_database_encoding_max_length() + 1;
				fc_result = palloc(fc_result_size);

				wchar2char(fc_result, fc_workspace, fc_result_size, fc_mylocale);
				pfree(fc_workspace);
			}
			else
			{
				char	   *fc_p;

				fc_result = pnstrdup(fc_buff, fc_nbytes);

				/*
				 * 注意：我们假设 toupper_l() 不会坏到需要 islower_l() 的保护测试。当使用默认
				 * 排序时，我们应用传统的 Postgres 行为，强制 ASCII 风格处理 I/i，但在非默认
				 * 排序中，你会得到排序所指定的内容。
				 */
				for (fc_p = fc_result; *fc_p; fc_p++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
						*fc_p = toupper_l((unsigned char) *fc_p, fc_mylocale->info.lt);
					else
#endif
						*fc_p = pg_toupper((unsigned char) *fc_p);
				}
			}
		}
	}

	return fc_result;
}

/*
 * 具有排序感知和宽字符感知的 initcap 函数
 *
 * 我们传递字节数，以便能够将 varlena 和 char* 传递给此函数。结果是一个 palloc 分配的、以 null 结尾的字符串。
 */
char * str_initcap(const char *fc_buff, size_t fc_nbytes, Oid fc_collid)
{
	char	   *fc_result;
	int			fc_wasalnum = false;

	if (!fc_buff)
		return NULL;

	if (!OidIsValid(fc_collid))
	{
		/*
		 * 这通常意味着解析器无法解决隐式排序的冲突，
		 * 因此以这种方式报告。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INDETERMINATE_COLLATION),
				 errmsg("could not determine which collation to use for %s function",
						"initcap()"),
				 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}

	/* C/POSIX比较无论数据库编码如何都使用此路径 */
	if (lc_ctype_is_c(fc_collid))
	{
		fc_result = asc_initcap(fc_buff, fc_nbytes);
	}
	else
	{
		pg_locale_t fc_mylocale;

		fc_mylocale = pg_newlocale_from_collation(fc_collid);

#ifdef USE_ICU
		if (fc_mylocale && fc_mylocale->provider == COLLPROVIDER_ICU)
		{
			int32_t		fc_len_uchar,
						fc_len_conv;
			UChar	   *fc_buff_uchar;
			UChar	   *fc_buff_conv;

			fc_len_uchar = icu_to_uchar(&fc_buff_uchar, fc_buff, fc_nbytes);
			fc_len_conv = fc_icu_convert_case(fc_u_strToTitle_default_BI, fc_mylocale,
										&fc_buff_conv, fc_buff_uchar, fc_len_uchar);
			icu_from_uchar(&fc_result, fc_buff_conv, fc_len_conv);
			pfree(fc_buff_uchar);
			pfree(fc_buff_conv);
		}
		else
#endif
		{
			if (pg_database_encoding_max_length() > 1)
			{
				wchar_t    *fc_workspace;
				size_t		fc_curr_char;
				size_t		fc_result_size;

				/* 溢出担忧 */
				if ((fc_nbytes + 1) > (INT_MAX / sizeof(wchar_t)))
					ereport(ERROR,
							(errcode(ERRCODE_OUT_OF_MEMORY),
							 errmsg("out of memory")));

				/* 输出工作区不能拥有比输入字节更多的代码 */
				fc_workspace = (wchar_t *) palloc((fc_nbytes + 1) * sizeof(wchar_t));

				char2wchar(fc_workspace, fc_nbytes + 1, fc_buff, fc_nbytes, fc_mylocale);

				for (fc_curr_char = 0; fc_workspace[fc_curr_char] != 0; fc_curr_char++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
					{
						if (fc_wasalnum)
							fc_workspace[fc_curr_char] = towlower_l(fc_workspace[fc_curr_char], fc_mylocale->info.lt);
						else
							fc_workspace[fc_curr_char] = towupper_l(fc_workspace[fc_curr_char], fc_mylocale->info.lt);
						fc_wasalnum = iswalnum_l(fc_workspace[fc_curr_char], fc_mylocale->info.lt);
					}
					else
#endif
					{
						if (fc_wasalnum)
							fc_workspace[fc_curr_char] = towlower(fc_workspace[fc_curr_char]);
						else
							fc_workspace[fc_curr_char] = towupper(fc_workspace[fc_curr_char]);
						fc_wasalnum = iswalnum(fc_workspace[fc_curr_char]);
					}
				}

				/*
				 * 使结果足够大；大小写变化可能会改变字节数
				 */
				fc_result_size = fc_curr_char * pg_database_encoding_max_length() + 1;
				fc_result = palloc(fc_result_size);

				wchar2char(fc_result, fc_workspace, fc_result_size, fc_mylocale);
				pfree(fc_workspace);
			}
			else
			{
				char	   *fc_p;

				fc_result = pnstrdup(fc_buff, fc_nbytes);

				/*
				 * 注意：我们假设 toupper_l()/tolower_l() 不会坏到需要保护测试。当使用默认
				 * 排序时，我们应用传统的 Postgres 行为，强制 ASCII 风格处理 I/i，但在非默认
				 * 排序中，你会得到排序所指定的内容。
				 */
				for (fc_p = fc_result; *fc_p; fc_p++)
				{
#ifdef HAVE_LOCALE_T
					if (fc_mylocale)
					{
						if (fc_wasalnum)
							*fc_p = tolower_l((unsigned char) *fc_p, fc_mylocale->info.lt);
						else
							*fc_p = toupper_l((unsigned char) *fc_p, fc_mylocale->info.lt);
						fc_wasalnum = isalnum_l((unsigned char) *fc_p, fc_mylocale->info.lt);
					}
					else
#endif
					{
						if (fc_wasalnum)
							*fc_p = pg_tolower((unsigned char) *fc_p);
						else
							*fc_p = pg_toupper((unsigned char) *fc_p);
						fc_wasalnum = isalnum((unsigned char) *fc_p);
					}
				}
			}
		}
	}

	return fc_result;
}

/*
 * 仅限 ASCII 的 lower 函数
 *
 * 我们传递字节数，以便能够将 varlena 和 char* 传递给此函数。结果是一个 palloc 分配的、以 null 结尾的字符串。
 */
char * asc_tolower(const char *fc_buff, size_t fc_nbytes)
{
	char	   *fc_result;
	char	   *fc_p;

	if (!fc_buff)
		return NULL;

	fc_result = pnstrdup(fc_buff, fc_nbytes);

	for (fc_p = fc_result; *fc_p; fc_p++)
		*fc_p = pg_ascii_tolower((unsigned char) *fc_p);

	return fc_result;
}

/*
 * 仅限 ASCII 的 upper 函数
 *
 * 我们传递字节数，以便能够将 varlena 和 char* 传递给此函数。结果是一个 palloc 分配的、以 null 结尾的字符串。
 */
char * asc_toupper(const char *fc_buff, size_t fc_nbytes)
{
	char	   *fc_result;
	char	   *fc_p;

	if (!fc_buff)
		return NULL;

	fc_result = pnstrdup(fc_buff, fc_nbytes);

	for (fc_p = fc_result; *fc_p; fc_p++)
		*fc_p = pg_ascii_toupper((unsigned char) *fc_p);

	return fc_result;
}

/*
 * 仅限 ASCII 的 initcap 函数
 *
 * 我们传递字节数，以便能够将 varlena 和 char* 传递给此函数。结果是一个 palloc 分配的、以 null 结尾的字符串。
 */
char * asc_initcap(const char *fc_buff, size_t fc_nbytes)
{
	char	   *fc_result;
	char	   *fc_p;
	int			fc_wasalnum = false;

	if (!fc_buff)
		return NULL;

	fc_result = pnstrdup(fc_buff, fc_nbytes);

	for (fc_p = fc_result; *fc_p; fc_p++)
	{
		char		fc_c;

		if (fc_wasalnum)
			*fc_p = fc_c = pg_ascii_tolower((unsigned char) *fc_p);
		else
			*fc_p = fc_c = pg_ascii_toupper((unsigned char) *fc_p);
		/* 我们在这里不信任 isalnum() */
		fc_wasalnum = ((fc_c >= 'A' && fc_c <= 'Z') ||
					(fc_c >= 'a' && fc_c <= 'z') ||
					(fc_c >= '0' && fc_c <= '9'));
	}

	return fc_result;
}

/* 输入为 null 结尾时的便利例程 */

static char * fc_str_tolower_z(const char *fc_buff, Oid fc_collid)
{
	return str_tolower(fc_buff, strlen(fc_buff), fc_collid);
}

static char * fc_str_toupper_z(const char *fc_buff, Oid fc_collid)
{
	return str_toupper(fc_buff, strlen(fc_buff), fc_collid);
}

static char * fc_str_initcap_z(const char *fc_buff, Oid fc_collid)
{
	return str_initcap(fc_buff, strlen(fc_buff), fc_collid);
}

static char * fc_asc_tolower_z(const char *fc_buff)
{
	return asc_tolower(fc_buff, strlen(fc_buff));
}

static char * fc_asc_toupper_z(const char *fc_buff)
{
	return asc_toupper(fc_buff, strlen(fc_buff));
}

/* asc_initcap_z 当前不需要 */


/* ----------
 * 跳过 TM / th 在 FROM_CHAR 中
 *
 * 如果 S_THth 处于开启状态，跳过两个字符，假设有两个可用
 * ----------
 */
#define SKIP_THth(ptr, _suf) \
	do { \
		if (S_THth(_suf)) \
		{ \
			if (*(ptr)) (ptr) += pg_mblen(ptr); \
			if (*(ptr)) (ptr) += pg_mblen(ptr); \
		} \
	} while (0)


#ifdef DEBUG_TO_FROM_CHAR
/* -----------
 * 调试：用于调试和索引检查的调用；（显示 ASCII 字符
 * 和每个使用位置的定义关键字
 * ----------
 */
static void fc_dump_index(const KeyWord *fc_k, const int *fc_index)
{
	int			fc_i,
				fc_count = 0,
				fc_free_i = 0;

	elog(DEBUG_elog_output, "TO-FROM_CHAR: Dump KeyWord Index:");

	for (fc_i = 0; fc_i < KeyWord_INDEX_SIZE; fc_i++)
	{
		if (fc_index[fc_i] != -1)
		{
			elog(DEBUG_elog_output, "\t%c: %s, ", fc_i + 32, fc_k[fc_index[fc_i]].name);
			fc_count++;
		}
		else
		{
			fc_free_i++;
			elog(DEBUG_elog_output, "\t(%d) %c %d", fc_i, fc_i + 32, fc_index[fc_i]);
		}
	}
	elog(DEBUG_elog_output, "\n\t\tUsed positions: %d,\n\t\tFree positions: %d",
		 fc_count, fc_free_i);
}
#endif							/* DEBUG */

/* ----------
 * 如果下一个格式图像不是数字值则返回 true
 * ----------
 */
static bool fc_is_next_separator(FormatNode *fc_n)
{
	if (fc_n->type == NODE_TYPE_END)
		return false;

	if (fc_n->type == NODE_TYPE_ACTION && S_THth(fc_n->suffix))
		return true;

	/*
	 * 下一个节点
	 */
	fc_n++;

	/* 格式字符串的结束被视为非数字分隔符 */
	if (fc_n->type == NODE_TYPE_END)
		return true;

	if (fc_n->type == NODE_TYPE_ACTION)
	{
		if (fc_n->key->is_digit)
			return false;

		return true;
	}
	else if (fc_n->character[1] == '\0' &&
			 isdigit((unsigned char) fc_n->character[0]))
		return false;

	return true;				/* 一些非数字输入（分隔符） */
}


static int fc_adjust_partial_year_to_2020(int fc_year)
{
	/*
	 * 调整所有日期朝向 2020；这实际上是我们假设 '70' 是 1970 并且 '69' 是 2069 时发生的情况。
	 */
	/* 强制 0-69 进入 2000 年代 */
	if (fc_year < 70)
		return fc_year + 2000;
	/* 强制 70-99 进入 1900 年代 */
	else if (fc_year < 100)
		return fc_year + 1900;
	/* 强制 100-519 进入 2000 年代 */
	else if (fc_year < 520)
		return fc_year + 2000;
	/* 强制 520-999 进入 1000 年代 */
	else if (fc_year < 1000)
		return fc_year + 1000;
	else
		return fc_year;
}


static int fc_strspace_len(const char *fc_str)
{
	int			fc_len = 0;

	while (*fc_str && isspace((unsigned char) *fc_str))
	{
		fc_str++;
		fc_len++;
	}
	return fc_len;
}

/*
 * 设置 from-char 转换的日期模式。
 *
 * 如果日期模式已经设置，并且调用者试图将其设置为冲突模式，则抛出错误。
 *
 * 如果 'have_error' 为 NULL，则会抛出错误，否则将设置 '*have_error'。
 */
static void fc_from_char_set_mode(TmFromChar *fc_tmfc, const FromCharDateMode fc_mode, bool *fc_have_error)
{
	if (fc_mode != FROM_CHAR_DATE_NONE)
	{
		if (fc_tmfc->mode == FROM_CHAR_DATE_NONE)
			fc_tmfc->mode = fc_mode;
		else if (fc_tmfc->mode != fc_mode)
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("invalid combination of date conventions"),
								  errhint("Do not mix Gregorian and ISO week date "
										  "conventions in a formatting template."))));
	}

on_error:
	return;
}

/*
 * 将指向 'dest' 的整数设置为给定值。
 *
 * 如果目标整数之前已设置为其他非零值，则抛出错误。
 *
 * 如果 'have_error' 为 NULL，则会抛出错误，否则将设置 '*have_error'。
 */
static void fc_from_char_set_int(int *fc_dest, const int fc_value, const FormatNode *fc_node,
				  bool *fc_have_error)
{
	if (*fc_dest != 0 && *fc_dest != fc_value)
		RETURN_ERROR(ereport(ERROR,
							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
							  errmsg("conflicting values for \"%s\" field in "
									 "formatting string",
									 fc_node->key->name),
							  errdetail("This value contradicts a previous setting "
										"for the same field type."))));
	*fc_dest = fc_value;

on_error:
	return;
}

/*
 * 从源字符串中读取单个整数，放入指向 'dest' 的整数中。如果 'dest' 为 NULL，则结果被丢弃。
 *
 * 在固定宽度模式下（节点没有 FM 后缀），最多消耗 'len' 个字符。然而，任何前导空白不会计入 'len' 中。
 *
 * 我们使用 strtol() 从源字符串中恢复整数值，符合给定的 FormatNode。
 *
 * 如果转换成功完成，src 将被推进到指向紧跟在转换中使用的最后一个字符之后的字符。
 *
 * 返回消耗的字符数。
 *
 * 注意，from_char_parse_int() 提供了一个更方便的包装，其中字段的长度与格式关键字的长度相同（如 DD 和 MI）。
 *
 * 如果 'have_error' 为 NULL，则会抛出错误，否则将设置 '*have_error' 并返回 -1。
 */
static int fc_from_char_parse_int_len(int *fc_dest, const char **fc_src, const int fc_len, FormatNode *fc_node,
						bool *fc_have_error)
{
	long		fc_result;
	char		fc_copy[DCH_MAX_ITEM_SIZ + 1];
	const char *fc_init = *fc_src;
	int			fc_used;

	/*
	 * 在解析整数之前跳过任何空白。
	 */
	*fc_src += fc_strspace_len(*fc_src);

	Assert(fc_len <= DCH_MAX_ITEM_SIZ);
	fc_used = (int) strlcpy(fc_copy, *fc_src, fc_len + 1);

	if (S_FM(fc_node->suffix) || fc_is_next_separator(fc_node))
	{
		/*
		 * 此节点处于填充模式，或者下一个节点已知是非数字值，因此我们只需尽可能多地读取字符。
		 */
		char	   *fc_endptr;

		errno = 0;
		fc_result = strtol(fc_init, &fc_endptr, 10);
		*fc_src = fc_endptr;
	}
	else
	{
		/*
		 * 我们需要从字符串中抽取正好在 'len' 中给定数量的字符，并转换它们。
		 */
		char	   *fc_last;

		if (fc_used < fc_len)
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("source string too short for \"%s\" "
										 "formatting field",
										 fc_node->key->name),
								  errdetail("Field requires %d characters, "
											"but only %d remain.",
											fc_len, fc_used),
								  errhint("If your source string is not fixed-width, "
										  "try using the \"FM\" modifier."))));

		errno = 0;
		fc_result = strtol(fc_copy, &fc_last, 10);
		fc_used = fc_last - fc_copy;

		if (fc_used > 0 && fc_used < fc_len)
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("invalid value \"%s\" for \"%s\"",
										 fc_copy, fc_node->key->name),
								  errdetail("Field requires %d characters, "
											"but only %d could be parsed.",
											fc_len, fc_used),
								  errhint("If your source string is not fixed-width, "
										  "try using the \"FM\" modifier."))));

		*fc_src += fc_used;
	}

	if (*fc_src == fc_init)
		RETURN_ERROR(ereport(ERROR,
							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
							  errmsg("invalid value \"%s\" for \"%s\"",
									 fc_copy, fc_node->key->name),
							  errdetail("Value must be an integer."))));

	if (errno == ERANGE || fc_result < INT_MIN || fc_result > INT_MAX)
		RETURN_ERROR(ereport(ERROR,
							 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
							  errmsg("value for \"%s\" in source string is out of range",
									 fc_node->key->name),
							  errdetail("Value must be in the range %d to %d.",
										INT_MIN, INT_MAX))));

	if (fc_dest != NULL)
	{
		fc_from_char_set_int(fc_dest, (int) fc_result, fc_node, fc_have_error);
		CHECK_ERROR;
	}

	return *fc_src - fc_init;

on_error:
	return -1;
}


/*
 * 从 from_char_parse_int_len() 调用，使用格式关键字的长度作为
 * 字段的预期长度。
 *
 * 如果字段的长度与格式关键字不同，请不要调用此函数
 * （例如 HH24；关键字长度为 4，但字段长度为 2）。
 * 在这种情况下，请调用 from_char_parse_int_len() 来明确指定所需长度。
 */
static int fc_from_char_parse_int(int *fc_dest, const char **fc_src, FormatNode *fc_node, bool *fc_have_error)
{
	return fc_from_char_parse_int_len(fc_dest, fc_src, fc_node->key->len, fc_node, fc_have_error);
}

/*
 * 顺序搜索以空字符结尾的 "array"，寻找与 "name" 的初始字符
 * 进行不区分大小写的匹配。
 *
 * 返回匹配的数组索引，或 -1 表示没有匹配。
 *
 * *len 被设置为匹配的长度，或 0 表示没有匹配。
 *
 * 不区分大小写的定义是根据 pg_ascii_tolower，因此这仅适用于与
 * ASCII 字符串的比较。
 */
static int fc_seq_search_ascii(const char *fc_name, const char *const *fc_array, int *fc_len)
{
	unsigned char fc_firstc;
	const char *const *fc_a;

	*fc_len = 0;

	/* 空字符串无法匹配任何东西 */
	if (!*fc_name)
		return -1;

	/* 我们特别处理第一个字符以提高速度 */
	fc_firstc = pg_ascii_tolower((unsigned char) *fc_name);

	for (fc_a = fc_array; *fc_a != NULL; fc_a++)
	{
		const char *fc_p;
		const char *fc_n;

		/* 比较第一个字符 */
		if (pg_ascii_tolower((unsigned char) **fc_a) != fc_firstc)
			continue;

		/* 比较字符串的其余部分 */
		for (fc_p = *fc_a + 1, fc_n = fc_name + 1;; fc_p++, fc_n++)
		{
			/* 如果我们匹配了整个数组条目，则返回成功 */
			if (*fc_p == '\0')
			{
				*fc_len = fc_n - fc_name;
				return fc_a - fc_array;
			}
			/* 否则，"name" 中必须有另一个字符... */
			if (*fc_n == '\0')
				break;
			/* ...并且它必须匹配 */
			if (pg_ascii_tolower((unsigned char) *fc_p) !=
				pg_ascii_tolower((unsigned char) *fc_n))
				break;
		}
	}

	return -1;
}

/*
 * 顺序搜索可能非英语单词的数组，以查找与 "name" 的初始字符
 * 大小写不敏感的匹配。
 *
 * 这与 seq_search_ascii() 的 API 相同，但我们使用更一般
 * 的大小写折叠转换来实现大小写不敏感。大小写折叠
 * 是根据 "collid" 确定的比较规则进行的。
 *
 * 该数组被视为常量，但我们不声明它，因为
 * pg_locale.c 导出的数组不是常量。
 */
static int fc_seq_search_localized(const char *fc_name, char **fc_array, int *fc_len, Oid fc_collid)
{
	char	  **fc_a;
	char	   *fc_upper_name;
	char	   *fc_lower_name;

	*fc_len = 0;

	/* 空字符串无法匹配任何东西 */
	if (!*fc_name)
		return -1;

	/*
	 * 下面的大小写折叠处理相当昂贵，因此在执行
	 * 这一步之前，快速检查一下是否存在精确匹配。
	 */
	for (fc_a = fc_array; *fc_a != NULL; fc_a++)
	{
		int			fc_element_len = strlen(*fc_a);

		if (strncmp(fc_name, *fc_a, fc_element_len) == 0)
		{
			*fc_len = fc_element_len;
			return fc_a - fc_array;
		}
	}

	/*
	 * 先折叠为大写，再折叠为小写，以便我们能够可靠地匹配
	 * 即使在大小写转换不是单射的语言中也是如此。
	 */
	fc_upper_name = str_toupper(unconstify(char *, fc_name), strlen(fc_name), fc_collid);
	fc_lower_name = str_tolower(fc_upper_name, strlen(fc_upper_name), fc_collid);
	pfree(fc_upper_name);

	for (fc_a = fc_array; *fc_a != NULL; fc_a++)
	{
		char	   *fc_upper_element;
		char	   *fc_lower_element;
		int			fc_element_len;

		/* 同样，上/下案例数组元素 */
		fc_upper_element = str_toupper(*fc_a, strlen(*fc_a), fc_collid);
		fc_lower_element = str_tolower(fc_upper_element, strlen(fc_upper_element),
									fc_collid);
		pfree(fc_upper_element);
		fc_element_len = strlen(fc_lower_element);

		/* 匹配? */
		if (strncmp(fc_lower_name, fc_lower_element, fc_element_len) == 0)
		{
			*fc_len = fc_element_len;
			pfree(fc_lower_element);
			pfree(fc_lower_name);
			return fc_a - fc_array;
		}
		pfree(fc_lower_element);
	}

	pfree(fc_lower_name);
	return -1;
}

/*
 * 在 'array' 中进行顺序搜索（或 'localized_array'，如果
 * 不是 NULL）以查找与 'src' 字符串的首个字符
 * 大小写不敏感的匹配。
 *
 * 'array' 被认为是英语单词（全 ASCII），但
 * 如果提供了 'localized_array'，那可能是非英语
 * 的，因此我们需要一个更昂贵的大小写折叠转换
 * （将遵循 'collid' 的比较规则）。
 *
 * 如果找到匹配项，将匹配的数组索引复制到由 'dest' 指向的整数中，
 * 将 'src' 向前推进到匹配字符部分的末尾，
 * 并返回消耗的字符数。
 *
 * 如果字符串不匹配，如果 'have_error' 为 NULL，则抛出错误，
 * 否则设置 '*have_error' 并返回 -1。
 *
 * 'node' 仅用于错误报告：node->key->name 标识
 * 我们正在搜索的字段类型。
 */
static int fc_from_char_seq_search(int *fc_dest, const char **fc_src, const char *const *fc_array,
					 char **fc_localized_array, Oid fc_collid,
					 FormatNode *fc_node, bool *fc_have_error)
{
	int			fc_len;

	if (fc_localized_array == NULL)
		*fc_dest = fc_seq_search_ascii(*fc_src, fc_array, &fc_len);
	else
		*fc_dest = fc_seq_search_localized(*fc_src, fc_localized_array, &fc_len, fc_collid);

	if (fc_len <= 0)
	{
		/*
		 * 在错误报告中，在下一个空白处截断字符串（如果有）
		 * 以避免包含无关数据。
		 */
		char	   *fc_copy = pstrdup(*fc_src);
		char	   *fc_c;

		for (fc_c = fc_copy; *fc_c; fc_c++)
		{
			if (scanner_isspace(*fc_c))
			{
				*fc_c = '\0';
				break;
			}
		}

		RETURN_ERROR(ereport(ERROR,
							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
							  errmsg("invalid value \"%s\" for \"%s\"",
									 fc_copy, fc_node->key->name),
							  errdetail("The given value did not match any of "
										"the allowed values for this field."))));
	}
	*fc_src += fc_len;
	return fc_len;

on_error:
	return -1;
}

/* ----------
 * 处理由 FormatNodes 列表表示的 TmToChar 结构。
 * 格式化的数据写入由 'out' 指向的字符串中。
 * ----------
 */
static void fc_DCH_to_char(FormatNode *fc_node, bool fc_is_interval, TmToChar *fc_in, char *fc_out, Oid fc_collid)
{
	FormatNode *fc_n;
	char	   *fc_s;
	struct fmt_tm *fc_tm = &fc_in->tm;
	int			fc_i;

	/* 缓存本地化的天和月 */
	cache_locale_time();

	fc_s = fc_out;
	for (fc_n = fc_node; fc_n->type != NODE_TYPE_END; fc_n++)
	{
		if (fc_n->type != NODE_TYPE_ACTION)
		{
			strcpy(fc_s, fc_n->character);
			fc_s += strlen(fc_s);
			continue;
		}

		switch (fc_n->key->id)
		{
			case DCH_A_M:
			case DCH_P_M:
				strcpy(fc_s, (fc_tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
					   ? P_M_STR : A_M_STR);
				fc_s += strlen(fc_s);
				break;
			case DCH_AM:
			case DCH_PM:
				strcpy(fc_s, (fc_tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
					   ? PM_STR : AM_STR);
				fc_s += strlen(fc_s);
				break;
			case DCH_a_m:
			case DCH_p_m:
				strcpy(fc_s, (fc_tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
					   ? p_m_STR : a_m_STR);
				fc_s += strlen(fc_s);
				break;
			case DCH_am:
			case DCH_pm:
				strcpy(fc_s, (fc_tm->tm_hour % HOURS_PER_DAY >= HOURS_PER_DAY / 2)
					   ? pm_STR : am_STR);
				fc_s += strlen(fc_s);
				break;
			case DCH_HH:
			case DCH_HH12:

				/*
				 * 以 12 小时制时钟显示时间，即使对于
				 * 间隔
				 */
				sprintf(fc_s, "%0*lld", S_FM(fc_n->suffix) ? 0 : (fc_tm->tm_hour >= 0) ? 2 : 3,
						fc_tm->tm_hour % (HOURS_PER_DAY / 2) == 0 ?
						(long long) (HOURS_PER_DAY / 2) :
						(long long) (fc_tm->tm_hour % (HOURS_PER_DAY / 2)));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_HH24:
				sprintf(fc_s, "%0*lld", S_FM(fc_n->suffix) ? 0 : (fc_tm->tm_hour >= 0) ? 2 : 3,
						(long long) fc_tm->tm_hour);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_MI:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : (fc_tm->tm_min >= 0) ? 2 : 3,
						fc_tm->tm_min);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_SS:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : (fc_tm->tm_sec >= 0) ? 2 : 3,
						fc_tm->tm_sec);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;

#define DCH_to_char_fsec(frac_fmt, frac_val) \
				sprintf(fc_s, frac_fmt, (int) (frac_val)); \
				if (S_THth(fc_n->suffix)) \
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix)); \
				fc_s += strlen(fc_s)

			case DCH_FF1:		/* 秒的十分之一 */
				DCH_to_char_fsec("%01d", fc_in->fsec / 100000);
				break;
			case DCH_FF2:		/* 秒的百分之一 */
				DCH_to_char_fsec("%02d", fc_in->fsec / 10000);
				break;
			case DCH_FF3:
			case DCH_MS:		/* 毫秒 */
				DCH_to_char_fsec("%03d", fc_in->fsec / 1000);
				break;
			case DCH_FF4:		/* 毫秒的十分之一 */
				DCH_to_char_fsec("%04d", fc_in->fsec / 100);
				break;
			case DCH_FF5:		/* 毫秒的百分之一 */
				DCH_to_char_fsec("%05d", fc_in->fsec / 10);
				break;
			case DCH_FF6:
			case DCH_US:		/* 微秒 */
				DCH_to_char_fsec("%06d", fc_in->fsec);
				break;
#undef DCH_to_char_fsec
			case DCH_SSSS:
				sprintf(fc_s, "%lld",
						(long long) (fc_tm->tm_hour * SECS_PER_HOUR +
									 fc_tm->tm_min * SECS_PER_MINUTE +
									 fc_tm->tm_sec));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_tz:
				INVALID_FOR_INTERVAL;
				if (tmtcTzn(fc_in))
				{
					/* 我们在这里假设时区名称不是本地化的 */
					char	   *fc_p = fc_asc_tolower_z(tmtcTzn(fc_in));

					strcpy(fc_s, fc_p);
					pfree(fc_p);
					fc_s += strlen(fc_s);
				}
				break;
			case DCH_TZ:
				INVALID_FOR_INTERVAL;
				if (tmtcTzn(fc_in))
				{
					strcpy(fc_s, tmtcTzn(fc_in));
					fc_s += strlen(fc_s);
				}
				break;
			case DCH_TZH:
				INVALID_FOR_INTERVAL;
				sprintf(fc_s, "%c%02d",
						(fc_tm->tm_gmtoff >= 0) ? '+' : '-',
						abs((int) fc_tm->tm_gmtoff) / SECS_PER_HOUR);
				fc_s += strlen(fc_s);
				break;
			case DCH_TZM:
				INVALID_FOR_INTERVAL;
				sprintf(fc_s, "%02d",
						(abs((int) fc_tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
				fc_s += strlen(fc_s);
				break;
			case DCH_OF:
				INVALID_FOR_INTERVAL;
				sprintf(fc_s, "%c%0*d",
						(fc_tm->tm_gmtoff >= 0) ? '+' : '-',
						S_FM(fc_n->suffix) ? 0 : 2,
						abs((int) fc_tm->tm_gmtoff) / SECS_PER_HOUR);
				fc_s += strlen(fc_s);
				if (abs((int) fc_tm->tm_gmtoff) % SECS_PER_HOUR != 0)
				{
					sprintf(fc_s, ":%02d",
							(abs((int) fc_tm->tm_gmtoff) % SECS_PER_HOUR) / SECS_PER_MINUTE);
					fc_s += strlen(fc_s);
				}
				break;
			case DCH_A_D:
			case DCH_B_C:
				INVALID_FOR_INTERVAL;
				strcpy(fc_s, (fc_tm->tm_year <= 0 ? B_C_STR : A_D_STR));
				fc_s += strlen(fc_s);
				break;
			case DCH_AD:
			case DCH_BC:
				INVALID_FOR_INTERVAL;
				strcpy(fc_s, (fc_tm->tm_year <= 0 ? BC_STR : AD_STR));
				fc_s += strlen(fc_s);
				break;
			case DCH_a_d:
			case DCH_b_c:
				INVALID_FOR_INTERVAL;
				strcpy(fc_s, (fc_tm->tm_year <= 0 ? b_c_STR : a_d_STR));
				fc_s += strlen(fc_s);
				break;
			case DCH_ad:
			case DCH_bc:
				INVALID_FOR_INTERVAL;
				strcpy(fc_s, (fc_tm->tm_year <= 0 ? bc_STR : ad_STR));
				fc_s += strlen(fc_s);
				break;
			case DCH_MONTH:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_toupper_z(localized_full_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							fc_asc_toupper_z(months_full[fc_tm->tm_mon - 1]));
				fc_s += strlen(fc_s);
				break;
			case DCH_Month:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_initcap_z(localized_full_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							months_full[fc_tm->tm_mon - 1]);
				fc_s += strlen(fc_s);
				break;
			case DCH_month:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_tolower_z(localized_full_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							fc_asc_tolower_z(months_full[fc_tm->tm_mon - 1]));
				fc_s += strlen(fc_s);
				break;
			case DCH_MON:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_toupper_z(localized_abbrev_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, fc_asc_toupper_z(months[fc_tm->tm_mon - 1]));
				fc_s += strlen(fc_s);
				break;
			case DCH_Mon:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_initcap_z(localized_abbrev_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, months[fc_tm->tm_mon - 1]);
				fc_s += strlen(fc_s);
				break;
			case DCH_mon:
				INVALID_FOR_INTERVAL;
				if (!fc_tm->tm_mon)
					break;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_tolower_z(localized_abbrev_months[fc_tm->tm_mon - 1], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, fc_asc_tolower_z(months[fc_tm->tm_mon - 1]));
				fc_s += strlen(fc_s);
				break;
			case DCH_MM:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : (fc_tm->tm_mon >= 0) ? 2 : 3,
						fc_tm->tm_mon);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_DAY:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_toupper_z(localized_full_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							fc_asc_toupper_z(days[fc_tm->tm_wday]));
				fc_s += strlen(fc_s);
				break;
			case DCH_Day:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_initcap_z(localized_full_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							days[fc_tm->tm_wday]);
				fc_s += strlen(fc_s);
				break;
			case DCH_day:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_tolower_z(localized_full_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -9,
							fc_asc_tolower_z(days[fc_tm->tm_wday]));
				fc_s += strlen(fc_s);
				break;
			case DCH_DY:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_toupper_z(localized_abbrev_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, fc_asc_toupper_z(days_short[fc_tm->tm_wday]));
				fc_s += strlen(fc_s);
				break;
			case DCH_Dy:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_initcap_z(localized_abbrev_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, days_short[fc_tm->tm_wday]);
				fc_s += strlen(fc_s);
				break;
			case DCH_dy:
				INVALID_FOR_INTERVAL;
				if (S_TM(fc_n->suffix))
				{
					char	   *fc_str = fc_str_tolower_z(localized_abbrev_days[fc_tm->tm_wday], fc_collid);

					if (strlen(fc_str) <= (fc_n->key->len + TM_SUFFIX_LEN) * DCH_MAX_ITEM_SIZ)
						strcpy(fc_s, fc_str);
					else
						ereport(ERROR,
								(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
								 errmsg("localized string format value too long")));
				}
				else
					strcpy(fc_s, fc_asc_tolower_z(days_short[fc_tm->tm_wday]));
				fc_s += strlen(fc_s);
				break;
			case DCH_DDD:
			case DCH_IDDD:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : 3,
						(fc_n->key->id == DCH_DDD) ?
						fc_tm->tm_yday :
						date2isoyearday(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_DD:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : 2, fc_tm->tm_mday);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_D:
				INVALID_FOR_INTERVAL;
				sprintf(fc_s, "%d", fc_tm->tm_wday + 1);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_ID:
				INVALID_FOR_INTERVAL;
				sprintf(fc_s, "%d", (fc_tm->tm_wday == 0) ? 7 : fc_tm->tm_wday);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_WW:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : 2,
						(fc_tm->tm_yday - 1) / 7 + 1);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_IW:
				sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : 2,
						date2isoweek(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_Q:
				if (!fc_tm->tm_mon)
					break;
				sprintf(fc_s, "%d", (fc_tm->tm_mon - 1) / 3 + 1);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_CC:
				if (fc_is_interval)	/* 直接计算 */
					fc_i = fc_tm->tm_year / 100;
				else
				{
					if (fc_tm->tm_year > 0)
						/* 二十世纪 == 1901 - 2000 */
						fc_i = (fc_tm->tm_year - 1) / 100 + 1;
					else
						/* 6BC世纪 == 600BC - 501BC */
						fc_i = fc_tm->tm_year / 100 - 1;
				}
				if (fc_i <= 99 && fc_i >= -99)
					sprintf(fc_s, "%0*d", S_FM(fc_n->suffix) ? 0 : (fc_i >= 0) ? 2 : 3, fc_i);
				else
					sprintf(fc_s, "%d", fc_i);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_Y_YYY:
				fc_i = ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) / 1000;
				sprintf(fc_s, "%d,%03d", fc_i,
						ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) - (fc_i * 1000));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_YYYY:
			case DCH_IYYY:
				sprintf(fc_s, "%0*d",
						S_FM(fc_n->suffix) ? 0 :
						(ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) >= 0) ? 4 : 5,
						(fc_n->key->id == DCH_YYYY ?
						 ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) :
						 ADJUST_YEAR(date2isoyear(fc_tm->tm_year,
												  fc_tm->tm_mon,
												  fc_tm->tm_mday),
									 fc_is_interval)));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_YYY:
			case DCH_IYY:
				sprintf(fc_s, "%0*d",
						S_FM(fc_n->suffix) ? 0 :
						(ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) >= 0) ? 3 : 4,
						(fc_n->key->id == DCH_YYY ?
						 ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) :
						 ADJUST_YEAR(date2isoyear(fc_tm->tm_year,
												  fc_tm->tm_mon,
												  fc_tm->tm_mday),
									 fc_is_interval)) % 1000);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_YY:
			case DCH_IY:
				sprintf(fc_s, "%0*d",
						S_FM(fc_n->suffix) ? 0 :
						(ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) >= 0) ? 2 : 3,
						(fc_n->key->id == DCH_YY ?
						 ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) :
						 ADJUST_YEAR(date2isoyear(fc_tm->tm_year,
												  fc_tm->tm_mon,
												  fc_tm->tm_mday),
									 fc_is_interval)) % 100);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_Y:
			case DCH_I:
				sprintf(fc_s, "%1d",
						(fc_n->key->id == DCH_Y ?
						 ADJUST_YEAR(fc_tm->tm_year, fc_is_interval) :
						 ADJUST_YEAR(date2isoyear(fc_tm->tm_year,
												  fc_tm->tm_mon,
												  fc_tm->tm_mday),
									 fc_is_interval)) % 10);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_RM:
				/* FALLTHROUGH */
			case DCH_rm:

				/*
				 * 对于间隔，像 '12 month' 这样的值将被减少为 0
				 * 个月和一些年份。这些应该被处理。
				 */
				if (!fc_tm->tm_mon && !fc_tm->tm_year)
					break;
				else
				{
					int			fc_mon = 0;
					const char *const *fc_months;

					if (fc_n->key->id == DCH_RM)
						fc_months = rm_months_upper;
					else
						fc_months = rm_months_lower;

					/*
					 * 计算在罗马数字数组中的位置。请注意，
					 * 数组的内容是反向的，十二月
					 * 放在首位， 一月放在最后。
					 */
					if (fc_tm->tm_mon == 0)
					{
						/*
						 * 这种情况是特殊的，跟踪完整
						 * 间隔年份的情况。
						 */
						fc_mon = fc_tm->tm_year >= 0 ? 0 : MONTHS_PER_YEAR - 1;
					}
					else if (fc_tm->tm_mon < 0)
					{
						/*
						 * 负数情况。在这种情况下，计算是
						 * 反向的，-1 代表十二月，-2 代表十一月，
						 * 等等。
						 */
						fc_mon = -1 * (fc_tm->tm_mon + 1);
					}
					else
					{
						/*
						 * 常见情况，值严格为正。数组中的位置与
						 * tm_mon 的值匹配。
						 */
						fc_mon = MONTHS_PER_YEAR - fc_tm->tm_mon;
					}

					sprintf(fc_s, "%*s", S_FM(fc_n->suffix) ? 0 : -4,
							fc_months[fc_mon]);
					fc_s += strlen(fc_s);
				}
				break;
			case DCH_W:
				sprintf(fc_s, "%d", (fc_tm->tm_mday - 1) / 7 + 1);
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
			case DCH_J:
				sprintf(fc_s, "%d", date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday));
				if (S_THth(fc_n->suffix))
					fc_str_numth(fc_s, fc_s, S_TH_TYPE(fc_n->suffix));
				fc_s += strlen(fc_s);
				break;
		}
	}

	*fc_s = '\0';
}

/*
 * 按照格式节点数组 'node[]' 指定的方式处理字符串 'in'。
 * 由 'out' 指向的 TmFromChar 结构体将填充结果。
 *
 * 'collid' 标识要使用的排序规则（如果需要）。
 * 'std' 指定标准解析模式。
 * 如果 'have_error' 为 NULL，则会抛出错误，否则 '*have_error' 会被设置。
 *
 * 注意：我们目前没有任何 to_interval() 函数，因此在这里不
 * 需要 INVALID_FOR_INTERVAL 检查。
 */
static void fc_DCH_from_char(FormatNode *fc_node, const char *fc_in, TmFromChar *fc_out,
			  Oid fc_collid, bool fc_std, bool *fc_have_error)
{
	FormatNode *fc_n;
	const char *fc_s;
	int			fc_len,
				fc_value;
	bool		fc_fx_mode = fc_std;

	/* 跳过的额外字符数量（超过格式字符串中给定的数量） */
	int			fc_extra_skip = 0;

	/* 缓存本地化的天和月 */
	cache_locale_time();

	for (fc_n = fc_node, fc_s = fc_in; fc_n->type != NODE_TYPE_END && *fc_s != '\0'; fc_n++)
	{
		/*
		 * 在字符串开头和字段前忽略空格，当不在 FX（固定宽度）模式时。
		 */
		if (!fc_fx_mode && (fc_n->type != NODE_TYPE_ACTION || fc_n->key->id != DCH_FX) &&
			(fc_n->type == NODE_TYPE_ACTION || fc_n == fc_node))
		{
			while (*fc_s != '\0' && isspace((unsigned char) *fc_s))
			{
				fc_s++;
				fc_extra_skip++;
			}
		}

		if (fc_n->type == NODE_TYPE_SPACE || fc_n->type == NODE_TYPE_SEPARATOR)
		{
			if (fc_std)
			{
				/*
				 * 标准模式要求格式字符串的分隔符/空格与输入字符串之间严格匹配。
				 */
				Assert(fc_n->character[0] && !fc_n->character[1]);

				if (*fc_s == fc_n->character[0])
					fc_s++;
				else
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
										  errmsg("unmatched format separator \"%c\"",
												 fc_n->character[0]))));
			}
			else if (!fc_fx_mode)
			{
				/*
				 * 在非 FX（固定格式）模式下，一个格式字符串中的空格或
				 * 分隔符与输入字符串中的一个空格或分隔符匹配。
				 * 如果当前输入字符串中的位置没有空格或分隔符，则不匹配任何内容。
				 */
				fc_extra_skip--;
				if (isspace((unsigned char) *fc_s) || fc_is_separator_char(fc_s))
				{
					fc_s++;
					fc_extra_skip++;
				}
			}
			else
			{
				/*
				 * 在 FX 模式下，对于格式字符串中的空格或分隔符，我们从输入字符串中消费
				 * 精确的一个字符。请注意，我们并不要求消耗的字符必须匹配格式中的
				 * 字符。
				 */
				fc_s += pg_mblen(fc_s);
			}
			continue;
		}
		else if (fc_n->type != NODE_TYPE_ACTION)
		{
			/*
			 * 文本字符，因此从输入字符串中消费一个字符。
			 * 请注意，我们并不要求消耗的字符必须匹配格式中的
			 * 字符。
			 */
			if (!fc_fx_mode)
			{
				/*
				 * 在非 FX 模式下，我们可能之前跳过了一些额外字符
				 * （超过格式字符串中指定的数量）。在这种情况下，
				 * 我们不跳过输入字符串中的字符，因为它可能
				 * 是字段的一部分。
				 */
				if (fc_extra_skip > 0)
					fc_extra_skip--;
				else
					fc_s += pg_mblen(fc_s);
			}
			else
			{
				int			fc_chlen = pg_mblen(fc_s);

				/*
				 * 标准模式要求格式字符之间严格匹配。
				 */
				if (fc_std && fc_n->type == NODE_TYPE_CHAR &&
					strncmp(fc_s, fc_n->character, fc_chlen) != 0)
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
										  errmsg("unmatched format character \"%s\"",
												 fc_n->character))));

				fc_s += fc_chlen;
			}
			continue;
		}

		fc_from_char_set_mode(fc_out, fc_n->key->date_mode, fc_have_error);
		CHECK_ERROR;

		switch (fc_n->key->id)
		{
			case DCH_FX:
				fc_fx_mode = true;
				break;
			case DCH_A_M:
			case DCH_P_M:
			case DCH_a_m:
			case DCH_p_m:
				fc_from_char_seq_search(&fc_value, &fc_s, ampm_strings_long,
									 NULL, InvalidOid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->pm, fc_value % 2, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->clock = CLOCK_12_HOUR;
				break;
			case DCH_AM:
			case DCH_PM:
			case DCH_am:
			case DCH_pm:
				fc_from_char_seq_search(&fc_value, &fc_s, ampm_strings,
									 NULL, InvalidOid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->pm, fc_value % 2, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->clock = CLOCK_12_HOUR;
				break;
			case DCH_HH:
			case DCH_HH12:
				fc_from_char_parse_int_len(&fc_out->hh, &fc_s, 2, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->clock = CLOCK_12_HOUR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_HH24:
				fc_from_char_parse_int_len(&fc_out->hh, &fc_s, 2, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_MI:
				fc_from_char_parse_int(&fc_out->mi, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_SS:
				fc_from_char_parse_int(&fc_out->ss, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_MS:		/* 毫秒 */
				fc_len = fc_from_char_parse_int_len(&fc_out->ms, &fc_s, 3, fc_n, fc_have_error);
				CHECK_ERROR;

				/*
				 * 25 是 0.25，250 也是 0.25；025 是 0.025，而不是 0.25
				 */
				fc_out->ms *= fc_len == 1 ? 100 :
					fc_len == 2 ? 10 : 1;

				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_FF1:
			case DCH_FF2:
			case DCH_FF3:
			case DCH_FF4:
			case DCH_FF5:
			case DCH_FF6:
				fc_out->ff = fc_n->key->id - DCH_FF1 + 1;
				/* fall through */
			case DCH_US:		/* 微秒 */
				fc_len = fc_from_char_parse_int_len(&fc_out->us, &fc_s,
											  fc_n->key->id == DCH_US ? 6 :
											  fc_out->ff, fc_n, fc_have_error);
				CHECK_ERROR;

				fc_out->us *= fc_len == 1 ? 100000 :
					fc_len == 2 ? 10000 :
					fc_len == 3 ? 1000 :
					fc_len == 4 ? 100 :
					fc_len == 5 ? 10 : 1;

				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_SSSS:
				fc_from_char_parse_int(&fc_out->ssss, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_tz:
			case DCH_TZ:
			case DCH_OF:
				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
									  errmsg("formatting field \"%s\" is only supported in to_char",
											 fc_n->key->name))));
				CHECK_ERROR;
				break;
			case DCH_TZH:

				/*
				 * TZH 的值可能为负。而问题在于我们可能会把负号作为分隔符吞掉。
				 * 因此，如果我们跳过的字符数量超过格式字符串中指定的数量，
				 * 则考虑将最后一个跳过的负号添加到 TZH 前面。
				 */
				if (*fc_s == '+' || *fc_s == '-' || *fc_s == ' ')
				{
					fc_out->tzsign = *fc_s == '-' ? -1 : +1;
					fc_s++;
				}
				else
				{
					if (fc_extra_skip > 0 && *(fc_s - 1) == '-')
						fc_out->tzsign = -1;
					else
						fc_out->tzsign = +1;
				}

				fc_from_char_parse_int_len(&fc_out->tzh, &fc_s, 2, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_TZM:
				/* 如果之前未看到 TZH，则分配正的时区符号 */
				if (!fc_out->tzsign)
					fc_out->tzsign = +1;
				fc_from_char_parse_int_len(&fc_out->tzm, &fc_s, 2, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_A_D:
			case DCH_B_C:
			case DCH_a_d:
			case DCH_b_c:
				fc_from_char_seq_search(&fc_value, &fc_s, adbc_strings_long,
									 NULL, InvalidOid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->bc, fc_value % 2, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_AD:
			case DCH_BC:
			case DCH_ad:
			case DCH_bc:
				fc_from_char_seq_search(&fc_value, &fc_s, adbc_strings,
									 NULL, InvalidOid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->bc, fc_value % 2, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_MONTH:
			case DCH_Month:
			case DCH_month:
				fc_from_char_seq_search(&fc_value, &fc_s, months_full,
									 S_TM(fc_n->suffix) ? localized_full_months : NULL,
									 fc_collid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->mm, fc_value + 1, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_MON:
			case DCH_Mon:
			case DCH_mon:
				fc_from_char_seq_search(&fc_value, &fc_s, months,
									 S_TM(fc_n->suffix) ? localized_abbrev_months : NULL,
									 fc_collid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->mm, fc_value + 1, fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_MM:
				fc_from_char_parse_int(&fc_out->mm, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_DAY:
			case DCH_Day:
			case DCH_day:
				fc_from_char_seq_search(&fc_value, &fc_s, days,
									 S_TM(fc_n->suffix) ? localized_full_days : NULL,
									 fc_collid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->d, fc_value, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->d++;
				break;
			case DCH_DY:
			case DCH_Dy:
			case DCH_dy:
				fc_from_char_seq_search(&fc_value, &fc_s, days_short,
									 S_TM(fc_n->suffix) ? localized_abbrev_days : NULL,
									 fc_collid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->d, fc_value, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->d++;
				break;
			case DCH_DDD:
				fc_from_char_parse_int(&fc_out->ddd, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_IDDD:
				fc_from_char_parse_int_len(&fc_out->ddd, &fc_s, 3, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_DD:
				fc_from_char_parse_int(&fc_out->dd, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_D:
				fc_from_char_parse_int(&fc_out->d, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_ID:
				fc_from_char_parse_int_len(&fc_out->d, &fc_s, 1, fc_n, fc_have_error);
				CHECK_ERROR;
				/* 将编号移位以匹配格里高利历，其中星期日 = 1 */
				if (++fc_out->d > 7)
					fc_out->d = 1;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_WW:
			case DCH_IW:
				fc_from_char_parse_int(&fc_out->ww, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_Q:

				/*
				 * 在转换为日期时我们忽略 'Q'，因为不清楚
				 * 使用季度中的哪一个日期，并且有些人指定
				 * 季度和月份，因此如果尊重它可能会
				 * 与提供的月份冲突。这也是我们不
				 * 抛出错误的原因。
				 *
				 * 我们仍然解析源字符串以获取整数，但它
				 * 不会存储在 'out' 中的任何地方。
				 */
				fc_from_char_parse_int((int *) NULL, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_CC:
				fc_from_char_parse_int(&fc_out->cc, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_Y_YYY:
				{
					int			fc_matched,
								fc_years,
								fc_millennia,
								fc_nch;

					fc_matched = sscanf(fc_s, "%d,%03d%n", &fc_millennia, &fc_years, &fc_nch);
					if (fc_matched < 2)
						RETURN_ERROR(ereport(ERROR,
											 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
											  errmsg("invalid input string for \"Y,YYY\""))));
					fc_years += (fc_millennia * 1000);
					fc_from_char_set_int(&fc_out->year, fc_years, fc_n, fc_have_error);
					CHECK_ERROR;
					fc_out->yysz = 4;
					fc_s += fc_nch;
					SKIP_THth(fc_s, fc_n->suffix);
				}
				break;
			case DCH_YYYY:
			case DCH_IYYY:
				fc_from_char_parse_int(&fc_out->year, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				fc_out->yysz = 4;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_YYY:
			case DCH_IYY:
				fc_len = fc_from_char_parse_int(&fc_out->year, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				if (fc_len < 4)
					fc_out->year = fc_adjust_partial_year_to_2020(fc_out->year);
				fc_out->yysz = 3;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_YY:
			case DCH_IY:
				fc_len = fc_from_char_parse_int(&fc_out->year, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				if (fc_len < 4)
					fc_out->year = fc_adjust_partial_year_to_2020(fc_out->year);
				fc_out->yysz = 2;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_Y:
			case DCH_I:
				fc_len = fc_from_char_parse_int(&fc_out->year, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				if (fc_len < 4)
					fc_out->year = fc_adjust_partial_year_to_2020(fc_out->year);
				fc_out->yysz = 1;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_RM:
			case DCH_rm:
				fc_from_char_seq_search(&fc_value, &fc_s, rm_months_lower,
									 NULL, InvalidOid,
									 fc_n, fc_have_error);
				CHECK_ERROR;
				fc_from_char_set_int(&fc_out->mm, MONTHS_PER_YEAR - fc_value,
								  fc_n, fc_have_error);
				CHECK_ERROR;
				break;
			case DCH_W:
				fc_from_char_parse_int(&fc_out->w, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
			case DCH_J:
				fc_from_char_parse_int(&fc_out->j, &fc_s, fc_n, fc_have_error);
				CHECK_ERROR;
				SKIP_THth(fc_s, fc_n->suffix);
				break;
		}

		/* 忽略字段后的所有空格 */
		if (!fc_fx_mode)
		{
			fc_extra_skip = 0;
			while (*fc_s != '\0' && isspace((unsigned char) *fc_s))
			{
				fc_s++;
				fc_extra_skip++;
			}
		}
	}

	/*
	 * 标准解析模式不允许输入字符串中存在不匹配的格式模式或
	 * 尾随字符。
	 */
	if (fc_std)
	{
		if (fc_n->type != NODE_TYPE_END)
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("input string is too short for datetime format"))));

		while (*fc_s != '\0' && isspace((unsigned char) *fc_s))
			fc_s++;

		if (*fc_s != '\0')
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("trailing characters remain in input string "
										 "after datetime format"))));
	}

on_error:
	return;
}

/*
 * DCH 缓存条目管理的不变性是 DCHCounter 等于
 * 当前条目中最大年龄值，每当访问发生时我们就递增它。
 * 如果接近溢出，通过将所有年龄值减半来处理，以便
 * 保持对哪些条目最旧的相对准确的概念。
 */
static inline void fc_DCH_prevent_counter_overflow(void)
{
	if (DCHCounter >= (INT_MAX - 1))
	{
		for (int fc_i = 0; fc_i < n_DCHCache; fc_i++)
			DCHCache[fc_i]->age >>= 1;
		DCHCounter >>= 1;
	}
}

/*
 * 获取在格式节点中存在的日期/时间/时区组件的掩码。
 *
 * 如果 'have_error' 为 NULL，则会抛出错误，否则 '*have_error' 会被设置。
 */
static int fc_DCH_datetime_type(FormatNode *fc_node, bool *fc_have_error)
{
	FormatNode *fc_n;
	int			fc_flags = 0;

	for (fc_n = fc_node; fc_n->type != NODE_TYPE_END; fc_n++)
	{
		if (fc_n->type != NODE_TYPE_ACTION)
			continue;

		switch (fc_n->key->id)
		{
			case DCH_FX:
				break;
			case DCH_A_M:
			case DCH_P_M:
			case DCH_a_m:
			case DCH_p_m:
			case DCH_AM:
			case DCH_PM:
			case DCH_am:
			case DCH_pm:
			case DCH_HH:
			case DCH_HH12:
			case DCH_HH24:
			case DCH_MI:
			case DCH_SS:
			case DCH_MS:		/* 毫秒 */
			case DCH_US:		/* 微秒 */
			case DCH_FF1:
			case DCH_FF2:
			case DCH_FF3:
			case DCH_FF4:
			case DCH_FF5:
			case DCH_FF6:
			case DCH_SSSS:
				fc_flags |= DCH_TIMED;
				break;
			case DCH_tz:
			case DCH_TZ:
			case DCH_OF:
				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
									  errmsg("formatting field \"%s\" is only supported in to_char",
											 fc_n->key->name))));
				fc_flags |= DCH_ZONED;
				break;
			case DCH_TZH:
			case DCH_TZM:
				fc_flags |= DCH_ZONED;
				break;
			case DCH_A_D:
			case DCH_B_C:
			case DCH_a_d:
			case DCH_b_c:
			case DCH_AD:
			case DCH_BC:
			case DCH_ad:
			case DCH_bc:
			case DCH_MONTH:
			case DCH_Month:
			case DCH_month:
			case DCH_MON:
			case DCH_Mon:
			case DCH_mon:
			case DCH_MM:
			case DCH_DAY:
			case DCH_Day:
			case DCH_day:
			case DCH_DY:
			case DCH_Dy:
			case DCH_dy:
			case DCH_DDD:
			case DCH_IDDD:
			case DCH_DD:
			case DCH_D:
			case DCH_ID:
			case DCH_WW:
			case DCH_Q:
			case DCH_CC:
			case DCH_Y_YYY:
			case DCH_YYYY:
			case DCH_IYYY:
			case DCH_YYY:
			case DCH_IYY:
			case DCH_YY:
			case DCH_IY:
			case DCH_Y:
			case DCH_I:
			case DCH_RM:
			case DCH_rm:
			case DCH_W:
			case DCH_J:
				fc_flags |= DCH_DATED;
				break;
		}
	}

on_error:
	return fc_flags;
}

/* 选择一个 DCHCacheEntry 来保存给定的格式图片 */
static DCHCacheEntry * fc_DCH_cache_getnew(const char *fc_str, bool fc_std)
{
	DCHCacheEntry *fc_ent;

	/* 确保我们可以提前 DCHCounter */
	fc_DCH_prevent_counter_overflow();

	/*
	 * 如果缓存已满，移除最旧的条目（或回收第一个无效条目）
	 */
	if (n_DCHCache >= DCH_CACHE_ENTRIES)
	{
		DCHCacheEntry *fc_old = DCHCache[0];

#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "cache is full (%d)", n_DCHCache);
#endif
		if (fc_old->valid)
		{
			for (int fc_i = 1; fc_i < DCH_CACHE_ENTRIES; fc_i++)
			{
				fc_ent = DCHCache[fc_i];
				if (!fc_ent->valid)
				{
					fc_old = fc_ent;
					break;
				}
				if (fc_ent->age < fc_old->age)
					fc_old = fc_ent;
			}
		}
#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "OLD: '%s' AGE: %d", fc_old->str, fc_old->age);
#endif
		fc_old->valid = false;
		strlcpy(fc_old->str, fc_str, DCH_CACHE_SIZE + 1);
		fc_old->age = (++DCHCounter);
		/* 调用者需要填写格式，然后设置为有效 */
		return fc_old;
	}
	else
	{
#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "NEW (%d)", n_DCHCache);
#endif
		Assert(DCHCache[n_DCHCache] == NULL);
		DCHCache[n_DCHCache] = fc_ent = (DCHCacheEntry *)
			MemoryContextAllocZero(TopMemoryContext, sizeof(DCHCacheEntry));
		fc_ent->valid = false;
		strlcpy(fc_ent->str, fc_str, DCH_CACHE_SIZE + 1);
		fc_ent->std = fc_std;
		fc_ent->age = (++DCHCounter);
		/* 调用者需要填写格式，然后设置为有效 */
		++n_DCHCache;
		return fc_ent;
	}
}

/* 查找与给定格式图片匹配的现有 DCHCacheEntry */
static DCHCacheEntry * fc_DCH_cache_search(const char *fc_str, bool fc_std)
{
	/* 确保我们可以提前 DCHCounter */
	fc_DCH_prevent_counter_overflow();

	for (int fc_i = 0; fc_i < n_DCHCache; fc_i++)
	{
		DCHCacheEntry *fc_ent = DCHCache[fc_i];

		if (fc_ent->valid && strcmp(fc_ent->str, fc_str) == 0 && fc_ent->std == fc_std)
		{
			fc_ent->age = (++DCHCounter);
			return fc_ent;
		}
	}

	return NULL;
}

/* 为给定格式图片查找或创建 DCHCacheEntry */
static DCHCacheEntry * fc_DCH_cache_fetch(const char *fc_str, bool fc_std)
{
	DCHCacheEntry *fc_ent;

	if ((fc_ent = fc_DCH_cache_search(fc_str, fc_std)) == NULL)
	{
		/*
		 * 不在缓存中，必须运行解析器并将新的格式图片保存到
		 * 缓存中。在解析成功之前，不要将缓存条目标记为有效。
		 */
		fc_ent = fc_DCH_cache_getnew(fc_str, fc_std);

		fc_parse_format(fc_ent->format, fc_str, DCH_keywords, DCH_suff, DCH_index,
					 DCH_FLAG | (fc_std ? STD_FLAG : 0), NULL);

		fc_ent->valid = true;
	}
	return fc_ent;
}

/*
 * 根据 fmt 格式化日期/时间或区间为字符串。
 * 我们将 fmt 解析为格式节点列表。然后将其传递给 DCH_to_char
 * 进行格式化。
 */
static text * fc_datetime_to_char_body(TmToChar *fc_tmtc, text *fc_fmt, bool fc_is_interval, Oid fc_collid)
{
	FormatNode *fc_format;
	char	   *fc_fmt_str,
			   *fc_result;
	bool		fc_incache;
	int			fc_fmt_len;
	text	   *fc_res;

	/*
	 * 将 fmt 转换为 C 字符串
	 */
	fc_fmt_str = text_to_cstring(fc_fmt);
	fc_fmt_len = strlen(fc_fmt_str);

	/*
	 * 为结果分配工作区作为 C 字符串
	 */
	fc_result = palloc((fc_fmt_len * DCH_MAX_ITEM_SIZ) + 1);
	*fc_result = '\0';

	if (fc_fmt_len > DCH_CACHE_SIZE)
	{
		/*
		 * 如果格式图片大于静态缓存，则分配新内存
		 * 并且始终调用解析器，不使用缓存
		 */
		fc_incache = false;

		fc_format = (FormatNode *) palloc((fc_fmt_len + 1) * sizeof(FormatNode));

		fc_parse_format(fc_format, fc_fmt_str, DCH_keywords,
					 DCH_suff, DCH_index, DCH_FLAG, NULL);
	}
	else
	{
		/*
		 * 使用缓存缓冲区
		 */
		DCHCacheEntry *fc_ent = fc_DCH_cache_fetch(fc_fmt_str, false);

		fc_incache = true;
		fc_format = fc_ent->format;
	}

	/* 真实的工作在这里 */
	fc_DCH_to_char(fc_format, fc_is_interval, fc_tmtc, fc_result, fc_collid);

	if (!fc_incache)
		pfree(fc_format);

	pfree(fc_fmt_str);

	/* 将C字符串结果转换为TEXT格式 */
	fc_res = cstring_to_text(fc_result);

	pfree(fc_result);
	return fc_res;
}

/****************************************************************************
 *				公共例程
 ***************************************************************************/

/* -------------------
 * TIMESTAMP to_char()
 * -------------------
 */
Datum timestamp_to_char(PG_FUNCTION_ARGS)
{
	Timestamp	fc_dt = PG_GETARG_TIMESTAMP(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1),
			   *fc_res;
	TmToChar	fc_tmtc;
	struct pg_tm fc_tt;
	struct fmt_tm *fc_tm;
	int			fc_thisdate;

	if (VARSIZE_ANY_EXHDR(fc_fmt) <= 0 || TIMESTAMP_NOT_FINITE(fc_dt))
		PG_RETURN_NULL();

	ZERO_tmtc(&fc_tmtc);
	fc_tm = tmtcTm(&fc_tmtc);

	if (timestamp2tm(fc_dt, NULL, &fc_tt, &tmtcFsec(&fc_tmtc), NULL, NULL) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	/* 计算wday和yday，因为timestamp2tm不计算 */
	fc_thisdate = date2j(fc_tt.tm_year, fc_tt.tm_mon, fc_tt.tm_mday);
	fc_tt.tm_wday = (fc_thisdate + 1) % 7;
	fc_tt.tm_yday = fc_thisdate - date2j(fc_tt.tm_year, 1, 1) + 1;

	COPY_tm(fc_tm, &fc_tt);

	if (!(fc_res = fc_datetime_to_char_body(&fc_tmtc, fc_fmt, false, PG_GET_COLLATION())))
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_res);
}

Datum timestamptz_to_char(PG_FUNCTION_ARGS)
{
	TimestampTz fc_dt = PG_GETARG_TIMESTAMP(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1),
			   *fc_res;
	TmToChar	fc_tmtc;
	int			fc_tz;
	struct pg_tm fc_tt;
	struct fmt_tm *fc_tm;
	int			fc_thisdate;

	if (VARSIZE_ANY_EXHDR(fc_fmt) <= 0 || TIMESTAMP_NOT_FINITE(fc_dt))
		PG_RETURN_NULL();

	ZERO_tmtc(&fc_tmtc);
	fc_tm = tmtcTm(&fc_tmtc);

	if (timestamp2tm(fc_dt, &fc_tz, &fc_tt, &tmtcFsec(&fc_tmtc), &tmtcTzn(&fc_tmtc), NULL) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	/* 计算wday和yday，因为timestamp2tm不计算 */
	fc_thisdate = date2j(fc_tt.tm_year, fc_tt.tm_mon, fc_tt.tm_mday);
	fc_tt.tm_wday = (fc_thisdate + 1) % 7;
	fc_tt.tm_yday = fc_thisdate - date2j(fc_tt.tm_year, 1, 1) + 1;

	COPY_tm(fc_tm, &fc_tt);

	if (!(fc_res = fc_datetime_to_char_body(&fc_tmtc, fc_fmt, false, PG_GET_COLLATION())))
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_res);
}


/* -------------------
 * INTERVAL to_char()
 * -------------------
 */
Datum interval_to_char(PG_FUNCTION_ARGS)
{
	Interval   *fc_it = PG_GETARG_INTERVAL_P(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1),
			   *fc_res;
	TmToChar	fc_tmtc;
	struct fmt_tm *fc_tm;
	struct pg_itm fc_tt,
			   *fc_itm = &fc_tt;

	if (VARSIZE_ANY_EXHDR(fc_fmt) <= 0)
		PG_RETURN_NULL();

	ZERO_tmtc(&fc_tmtc);
	fc_tm = tmtcTm(&fc_tmtc);

	interval2itm(*fc_it, fc_itm);
	fc_tmtc.fsec = fc_itm->tm_usec;
	fc_tm->tm_sec = fc_itm->tm_sec;
	fc_tm->tm_min = fc_itm->tm_min;
	fc_tm->tm_hour = fc_itm->tm_hour;
	fc_tm->tm_mday = fc_itm->tm_mday;
	fc_tm->tm_mon = fc_itm->tm_mon;
	fc_tm->tm_year = fc_itm->tm_year;

	/* wday没有意义，yday近似于总天数跨度 */
	fc_tm->tm_yday = (fc_tm->tm_year * MONTHS_PER_YEAR + fc_tm->tm_mon) * DAYS_PER_MONTH + fc_tm->tm_mday;

	if (!(fc_res = fc_datetime_to_char_body(&fc_tmtc, fc_fmt, true, PG_GET_COLLATION())))
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(fc_res);
}

/* ---------------------
 * TO_TIMESTAMP()
 *
 * 从格式为'fmt'的date_str中创建Timestamp
 * (to_timestamp是to_char()的反向操作)
 * ---------------------
 */
Datum to_timestamp(PG_FUNCTION_ARGS)
{
	text	   *fc_date_txt = PG_GETARG_TEXT_PP(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	Oid			fc_collid = PG_GET_COLLATION();
	Timestamp	fc_result;
	int			fc_tz;
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;
	int			fc_fprec;

	fc_do_to_timestamp(fc_date_txt, fc_fmt, fc_collid, false,
					&fc_tm, &fc_fsec, &fc_fprec, NULL, NULL);

	/* 如果有，使用指定的时区。 */
	if (fc_tm.tm_zone)
	{
		int			fc_dterr = DecodeTimezone(unconstify(char *, fc_tm.tm_zone), &fc_tz);

		if (fc_dterr)
			DateTimeParseError(fc_dterr, text_to_cstring(fc_date_txt), "timestamptz");
	}
	else
		fc_tz = DetermineTimeZoneOffset(&fc_tm, session_timezone);

	if (tm2timestamp(&fc_tm, fc_fsec, &fc_tz, &fc_result) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	/* 如果有，使用指定的小数精度。 */
	if (fc_fprec)
		AdjustTimestampForTypmod(&fc_result, fc_fprec);

	PG_RETURN_TIMESTAMP(fc_result);
}

/* ----------
 * TO_DATE
 *	从格式为'fmt'的date_str中创建日期
 * ----------
 */
Datum to_date(PG_FUNCTION_ARGS)
{
	text	   *fc_date_txt = PG_GETARG_TEXT_PP(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	Oid			fc_collid = PG_GET_COLLATION();
	DateADT		fc_result;
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;

	fc_do_to_timestamp(fc_date_txt, fc_fmt, fc_collid, false,
					&fc_tm, &fc_fsec, NULL, NULL, NULL);

	/* 防止 Julian 日 routines 溢出 */
	if (!IS_VALID_JULIAN(fc_tm.tm_year, fc_tm.tm_mon, tm.tm_mday))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("date out of range: \"%s\"",
						text_to_cstring(fc_date_txt))));

	fc_result = date2j(fc_tm.tm_year, fc_tm.tm_mon, fc_tm.tm_mday) - POSTGRES_EPOCH_JDATE;

	/* 现在检查超出范围的日期 */
	if (!IS_VALID_DATE(fc_result))
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("date out of range: \"%s\"",
						text_to_cstring(fc_date_txt))));

	PG_RETURN_DATEADT(fc_result);
}

/*
 * 使用格式字符串'fmt'将'date_txt'输入转换为datetime类型。
 * 某些情况下，可能会使用'collid'进行大小写折叠规则。'strict'指定标准解析模式。
 *
 * 实际数据类型（返回值中' typid'，'typmod'）由格式字符串中日期/时间/时区成分的存在决定。
 *
 * 当存在时区成分时，相应的偏移量将返回在'*tz'中。
 *
 * 如果'have_error'为NULL，则抛出错误，否则将'*have_error'设置
 * 并返回零值。
 */
Datum parse_datetime(text *fc_date_txt, text *fc_fmt, Oid fc_collid, bool fc_strict,
			   Oid *fc_typid, int32 *fc_typmod, int *fc_tz,
			   bool *fc_have_error)
{
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;
	int			fc_fprec;
	uint32		fc_flags;

	fc_do_to_timestamp(fc_date_txt, fc_fmt, fc_collid, fc_strict,
					&fc_tm, &fc_fsec, &fc_fprec, &fc_flags, fc_have_error);
	CHECK_ERROR;

	*fc_typmod = fc_fprec ? fc_fprec : -1;	/* 小数部分精度 */

	if (fc_flags & DCH_DATED)
	{
		if (fc_flags & DCH_TIMED)
		{
			if (fc_flags & DCH_ZONED)
			{
				TimestampTz fc_result;

				if (fc_tm.tm_zone)
				{
					int			fc_dterr = DecodeTimezone(unconstify(char *, fc_tm.tm_zone), fc_tz);

					if (fc_dterr)
						DateTimeParseError(fc_dterr, text_to_cstring(fc_date_txt), "timestamptz");
				}
				else
				{
					/*
					 * 格式字符串中存在时区，但输入字符串中不存在。
					 * 假设do_to_timestamp()不会触发错误，这只能在非严格模式下发生。
					 */
					Assert(!fc_strict);

					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
										  errmsg("missing time zone in input string for type timestamptz"))));
				}

				if (tm2timestamp(&fc_tm, fc_fsec, fc_tz, &fc_result) != 0)
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
										  errmsg("timestamptz out of range"))));

				AdjustTimestampForTypmod(&fc_result, *fc_typmod);

				*fc_typid = TIMESTAMPTZOID;
				return TimestampTzGetDatum(fc_result);
			}
			else
			{
				Timestamp	fc_result;

				if (tm2timestamp(&fc_tm, fc_fsec, NULL, &fc_result) != 0)
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
										  errmsg("timestamp out of range"))));

				AdjustTimestampForTypmod(&fc_result, *fc_typmod);

				*fc_typid = TIMESTAMPOID;
				return TimestampGetDatum(fc_result);
			}
		}
		else
		{
			if (fc_flags & DCH_ZONED)
			{
				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
									  errmsg("datetime format is zoned but not timed"))));
			}
			else
			{
				DateADT		fc_result;

				/* 防止 Julian 日 routines 溢出 */
				if (!IS_VALID_JULIAN(fc_tm.tm_year, fc_tm.tm_mon, tm.tm_mday))
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
										  errmsg("date out of range: \"%s\"",
												 text_to_cstring(fc_date_txt)))));

				fc_result = date2j(fc_tm.tm_year, fc_tm.tm_mon, fc_tm.tm_mday) -
					POSTGRES_EPOCH_JDATE;

				/* 现在检查超出范围的日期 */
				if (!IS_VALID_DATE(fc_result))
					RETURN_ERROR(ereport(ERROR,
										 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
										  errmsg("date out of range: \"%s\"",
												 text_to_cstring(fc_date_txt)))));

				*fc_typid = DATEOID;
				return DateADTGetDatum(fc_result);
			}
		}
	}
	else if (fc_flags & DCH_TIMED)
	{
		if (fc_flags & DCH_ZONED)
		{
			TimeTzADT  *fc_result = palloc(sizeof(TimeTzADT));

			if (fc_tm.tm_zone)
			{
				int			fc_dterr = DecodeTimezone(unconstify(char *, fc_tm.tm_zone), fc_tz);

				if (fc_dterr)
					RETURN_ERROR(DateTimeParseError(fc_dterr, text_to_cstring(fc_date_txt), "timetz"));
			}
			else
			{
				/*
				 * 格式字符串中存在时区，但输入字符串中不存在。
				 * 假设do_to_timestamp()不会触发错误，这只能在非严格模式下发生。
				 */
				Assert(!fc_strict);

				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
									  errmsg("missing time zone in input string for type timetz"))));
			}

			if (tm2timetz(&fc_tm, fc_fsec, *fc_tz, fc_result) != 0)
				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
									  errmsg("timetz out of range"))));

			AdjustTimeForTypmod(&fc_result->time, *fc_typmod);

			*fc_typid = TIMETZOID;
			return TimeTzADTPGetDatum(fc_result);
		}
		else
		{
			TimeADT		fc_result;

			if (tm2time(&fc_tm, fc_fsec, &fc_result) != 0)
				RETURN_ERROR(ereport(ERROR,
									 (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
									  errmsg("time out of range"))));

			AdjustTimeForTypmod(&fc_result, *fc_typmod);

			*fc_typid = TIMEOID;
			return TimeADTGetDatum(fc_result);
		}
	}
	else
	{
		RETURN_ERROR(ereport(ERROR,
							 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
							  errmsg("datetime format is not dated and not timed"))));
	}

on_error:
	return (Datum) 0;
}

/*
 * do_to_timestamp：to_timestamp和to_date的共享代码
 *
 * 根据'fmt'解析'date_txt'，以结构体pg_tm返回结果，
 * 小数秒和小数精度。
 *
 * 'collid'标识需要使用的排序规则（如果需要）。
 * 'std'指定标准解析模式。
 * 在'fmt'中找到的日期/时间/时区成分的位掩码将返回在'flags'中，
 * 如果它不为NULL。
 * 如果'have_error'为NULL，则抛出错误，否则将'*have_error'设置。
 *
 * 我们将'fmt'解析为FormatNodes的列表，然后传递给
 * DCH_from_char以填充具有解析内容的TmFromChar
 * 'date_txt'。
 *
 * 然后分析TmFromChar并转换成最终结果，
 * 在结构'tm'，'fsec'和'fprec'中。
 */
static void fc_do_to_timestamp(text *fc_date_txt, text *fc_fmt, Oid fc_collid, bool fc_std,
				struct pg_tm *fc_tm, fsec_t *fc_fsec, int *fc_fprec,
				uint32 *fc_flags, bool *fc_have_error)
{
	FormatNode *fc_format = NULL;
	TmFromChar	fc_tmfc;
	int			fc_fmt_len;
	char	   *fc_date_str;
	int			fc_fmask;
	bool		fc_incache = false;

	Assert(fc_tm != NULL);
	Assert(fc_fsec != NULL);

	fc_date_str = text_to_cstring(fc_date_txt);

	ZERO_tmfc(&fc_tmfc);
	ZERO_tm(fc_tm);
	*fc_fsec = 0;
	if (fc_fprec)
		*fc_fprec = 0;
	if (fc_flags)
		*fc_flags = 0;
	fc_fmask = 0;					/* ValidateDate()的位掩码 */

	fc_fmt_len = VARSIZE_ANY_EXHDR(fc_fmt);

	if (fc_fmt_len)
	{
		char	   *fc_fmt_str;

		fc_fmt_str = text_to_cstring(fc_fmt);

		if (fc_fmt_len > DCH_CACHE_SIZE)
		{
			/*
			 * 如果格式图大于静态缓存，则分配新内存
			 * 并且不使用缓存（始终调用解析器）
			 */
			fc_format = (FormatNode *) palloc((fc_fmt_len + 1) * sizeof(FormatNode));

			fc_parse_format(fc_format, fc_fmt_str, DCH_keywords, DCH_suff, DCH_index,
						 DCH_FLAG | (fc_std ? STD_FLAG : 0), NULL);
		}
		else
		{
			/*
			 * 使用缓存缓冲区
			 */
			DCHCacheEntry *fc_ent = fc_DCH_cache_fetch(fc_fmt_str, fc_std);

			fc_incache = true;
			fc_format = fc_ent->format;
		}

#ifdef DEBUG_TO_FROM_CHAR
		/* dump_node(format, fmt_len); */
		/* dump_index(DCH_keywords, DCH_index); */
#endif

		fc_DCH_from_char(fc_format, fc_date_str, &fc_tmfc, fc_collid, fc_std, fc_have_error);
		CHECK_ERROR;

		pfree(fc_fmt_str);

		if (fc_flags)
			*fc_flags = fc_DCH_datetime_type(fc_format, fc_have_error);

		if (!fc_incache)
		{
			pfree(fc_format);
			fc_format = NULL;
		}

		CHECK_ERROR;
	}

	DEBUG_TMFC(&fc_tmfc);

	/*
	 * 将to_date/to_timestamp输入字段转换为标准'tm'
	 */
	if (fc_tmfc.ssss)
	{
		int			fc_x = fc_tmfc.ssss;

		fc_tm->tm_hour = fc_x / SECS_PER_HOUR;
		fc_x %= SECS_PER_HOUR;
		fc_tm->tm_min = fc_x / SECS_PER_MINUTE;
		fc_x %= SECS_PER_MINUTE;
		fc_tm->tm_sec = fc_x;
	}

	if (fc_tmfc.ss)
		fc_tm->tm_sec = fc_tmfc.ss;
	if (fc_tmfc.mi)
		fc_tm->tm_min = fc_tmfc.mi;
	if (fc_tmfc.hh)
		fc_tm->tm_hour = fc_tmfc.hh;

	if (fc_tmfc.clock == CLOCK_12_HOUR)
	{
		if (fc_tm->tm_hour < 1 || fc_tm->tm_hour > HOURS_PER_DAY / 2)
		{
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("hour \"%d\" is invalid for the 12-hour clock",
										 fc_tm->tm_hour),
								  errhint("Use the 24-hour clock, or give an hour between 1 and 12."))));
		}

		if (fc_tmfc.pm && fc_tm->tm_hour < HOURS_PER_DAY / 2)
			fc_tm->tm_hour += HOURS_PER_DAY / 2;
		else if (!fc_tmfc.pm && fc_tm->tm_hour == HOURS_PER_DAY / 2)
			fc_tm->tm_hour = 0;
	}

	if (fc_tmfc.year)
	{
		/*
		 * 如果提供了CC和YY（或Y），则将YY作为给定世纪中
		 * 年份的两个低位数字。请记住，21世纪
		 * AD从2001年到2100年，而不是2000年到2099年；公元前6世纪
		 * 从公元前600年到公元前501年。
		 */
		if (fc_tmfc.cc && fc_tmfc.yysz <= 2)
		{
			if (fc_tmfc.bc)
				fc_tmfc.cc = -fc_tmfc.cc;
			fc_tm->tm_year = fc_tmfc.year % 100;
			if (fc_tm->tm_year)
			{
				if (fc_tmfc.cc >= 0)
					fc_tm->tm_year += (fc_tmfc.cc - 1) * 100;
				else
					fc_tm->tm_year = (fc_tmfc.cc + 1) * 100 - fc_tm->tm_year + 1;
			}
			else
			{
				/* 找到以“00”结尾的日期的世纪年份 */
				fc_tm->tm_year = fc_tmfc.cc * 100 + ((fc_tmfc.cc >= 0) ? 0 : 1);
			}
		}
		else
		{
			/* 如果提供了4位数年份，我们将使用它并忽略CC。 */
			fc_tm->tm_year = fc_tmfc.year;
			if (fc_tmfc.bc)
				fc_tm->tm_year = -fc_tm->tm_year;
			/* 修正我方对公元前年份的表示 */
			if (fc_tm->tm_year < 0)
				fc_tm->tm_year++;
		}
		fc_fmask |= DTK_M(YEAR);
	}
	else if (fc_tmfc.cc)
	{
		/* 使用世纪的第一年 */
		if (fc_tmfc.bc)
			fc_tmfc.cc = -fc_tmfc.cc;
		if (fc_tmfc.cc >= 0)
			/* +1因为21世纪从2001年开始 */
			fc_tm->tm_year = (fc_tmfc.cc - 1) * 100 + 1;
		else
			/* +1，因为年份 == 599 是公元前 600 年 */
			fc_tm->tm_year = fc_tmfc.cc * 100 + 1;
		fc_fmask |= DTK_M(YEAR);
	}

	if (fc_tmfc.j)
	{
		j2date(fc_tmfc.j, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
		fc_fmask |= DTK_DATE_M;
	}

	if (fc_tmfc.ww)
	{
		if (fc_tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
		{
			/*
			 * 如果 tmfc.d 未设置，则日期保留在 ISO 周的开始
			 * （周一）。
			 */
			if (fc_tmfc.d)
				isoweekdate2date(fc_tmfc.ww, fc_tmfc.d, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
			else
				isoweek2date(fc_tmfc.ww, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
			fc_fmask |= DTK_DATE_M;
		}
		else
			fc_tmfc.ddd = (fc_tmfc.ww - 1) * 7 + 1;
	}

	if (fc_tmfc.w)
		fc_tmfc.dd = (fc_tmfc.w - 1) * 7 + 1;
	if (fc_tmfc.dd)
	{
		fc_tm->tm_mday = fc_tmfc.dd;
		fc_fmask |= DTK_M(DAY);
	}
	if (fc_tmfc.mm)
	{
		fc_tm->tm_mon = fc_tmfc.mm;
		fc_fmask |= DTK_M(MONTH);
	}

	if (fc_tmfc.ddd && (fc_tm->tm_mon <= 1 || fc_tm->tm_mday <= 1))
	{
		/*
		 * 月和日字段尚未设置，因此我们使用
		 * 年中的天字段来填充它们。根据日期模式，
		 * 此字段可能被解释为公历的年天，或 ISO
		 * 周日期的年天。
		 */

		if (!fc_tm->tm_year && !fc_tmfc.bc)
		{
			RETURN_ERROR(ereport(ERROR,
								 (errcode(ERRCODE_INVALID_DATETIME_FORMAT),
								  errmsg("cannot calculate day of year without year information"))));
		}

		if (fc_tmfc.mode == FROM_CHAR_DATE_ISOWEEK)
		{
			int			fc_j0;		/* ISO 年的第零天，以儒略历表示 */

			fc_j0 = isoweek2j(fc_tm->tm_year, 1) - 1;

			j2date(fc_j0 + fc_tmfc.ddd, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
			fc_fmask |= DTK_DATE_M;
		}
		else
		{
			const int  *fc_y;
			int			fc_i;

			static const int fc_ysum[2][13] = {
				{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
			{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};

			fc_y = fc_ysum[isleap(fc_tm->tm_year)];

			for (fc_i = 1; fc_i <= MONTHS_PER_YEAR; fc_i++)
			{
				if (fc_tmfc.ddd <= fc_y[fc_i])
					break;
			}
			if (fc_tm->tm_mon <= 1)
				fc_tm->tm_mon = fc_i;

			if (fc_tm->tm_mday <= 1)
				fc_tm->tm_mday = fc_tmfc.ddd - fc_y[fc_i - 1];

			fc_fmask |= DTK_M(MONTH) | DTK_M(DAY);
		}
	}

	if (fc_tmfc.ms)
		*fc_fsec += fc_tmfc.ms * 1000;
	if (fc_tmfc.us)
		*fc_fsec += fc_tmfc.us;
	if (fc_fprec)
		*fc_fprec = fc_tmfc.ff;		/* 如果指定，则为小数精度 */

	/* 根据上述计算的位掩码范围检查日期字段 */
	if (fc_fmask != 0)
	{
		/* 我们已经处理了公元年/公元前，因此 pass isjulian = true */
		int			fc_dterr = ValidateDate(fc_fmask, true, false, false, fc_tm);

		if (fc_dterr != 0)
		{
			/*
			 * 强制将错误设置为 DTERR_FIELD_OVERFLOW，即使 ValidateDate
			 * 返回了 DTERR_MD_FIELD_OVERFLOW，因为我们不想打印一个
			 * 关于日期样式的不相关提示。
			 */
			RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, fc_date_str, "timestamp"));
		}
	}

	/* 也检查时间字段的范围 */
	if (fc_tm->tm_hour < 0 || fc_tm->tm_hour >= HOURS_PER_DAY ||
		fc_tm->tm_min < 0 || fc_tm->tm_min >= MINS_PER_HOUR ||
		fc_tm->tm_sec < 0 || fc_tm->tm_sec >= SECS_PER_MINUTE ||
		*fc_fsec < INT64CONST(0) || *fc_fsec >= USECS_PER_SEC)
	{
		RETURN_ERROR(DateTimeParseError(DTERR_FIELD_OVERFLOW, fc_date_str, "timestamp"));
	}

	/* 如果指定，则将解析的时区保存到 tm->tm_zone 中 */
	if (fc_tmfc.tzsign)
	{
		char	   *fc_tz;

		if (fc_tmfc.tzh < 0 || fc_tmfc.tzh > MAX_TZDISP_HOUR ||
			fc_tmfc.tzm < 0 || fc_tmfc.tzm >= MINS_PER_HOUR)
		{
			RETURN_ERROR(DateTimeParseError(DTERR_TZDISP_OVERFLOW, fc_date_str, "timestamp"));
		}

		fc_tz = psprintf("%c%02d:%02d",
					  fc_tmfc.tzsign > 0 ? '+' : '-', fc_tmfc.tzh, fc_tmfc.tzm);

		fc_tm->tm_zone = fc_tz;
	}

	DEBUG_TM(fc_tm);

on_error:

	if (fc_format && !fc_incache)
		pfree(fc_format);

	pfree(fc_date_str);
}


/**********************************************************************
 *	编号版本部分
 *********************************************************************/


static char * fc_fill_str(char *fc_str, int fc_c, int fc_max)
{
	memset(fc_str, fc_c, fc_max);
	*(fc_str + fc_max) = '\0';
	return fc_str;
}

#define zeroize_NUM(_n) \
do { \
	(_n)->flag		= 0;	\
	(_n)->lsign		= 0;	\
	(_n)->pre		= 0;	\
	(_n)->post		= 0;	\
	(_n)->pre_lsign_num = 0;	\
	(_n)->need_locale	= 0;	\
	(_n)->multi		= 0;	\
	(_n)->zero_start	= 0;	\
	(_n)->zero_end		= 0;	\
} while(0)

/* 这与 DCH_prevent_counter_overflow 的工作方式相同 */
static inline void fc_NUM_prevent_counter_overflow(void)
{
	if (NUMCounter >= (INT_MAX - 1))
	{
		for (int fc_i = 0; fc_i < n_NUMCache; fc_i++)
			NUMCache[fc_i]->age >>= 1;
		NUMCounter >>= 1;
	}
}

/* 选择一个 NUMCacheEntry 来保存给定的格式图片 */
static NUMCacheEntry * fc_NUM_cache_getnew(const char *fc_str)
{
	NUMCacheEntry *fc_ent;

	/* 确保我们可以向下推进 NUMCounter */
	fc_NUM_prevent_counter_overflow();

	/*
	 * 如果缓存已满，移除最旧的条目（或回收第一个无效条目）
	 */
	if (n_NUMCache >= NUM_CACHE_ENTRIES)
	{
		NUMCacheEntry *fc_old = NUMCache[0];

#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "Cache is full (%d)", n_NUMCache);
#endif
		if (fc_old->valid)
		{
			for (int fc_i = 1; fc_i < NUM_CACHE_ENTRIES; fc_i++)
			{
				fc_ent = NUMCache[fc_i];
				if (!fc_ent->valid)
				{
					fc_old = fc_ent;
					break;
				}
				if (fc_ent->age < fc_old->age)
					fc_old = fc_ent;
			}
		}
#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "OLD: \"%s\" AGE: %d", fc_old->str, fc_old->age);
#endif
		fc_old->valid = false;
		strlcpy(fc_old->str, fc_str, NUM_CACHE_SIZE + 1);
		fc_old->age = (++NUMCounter);
		/* 调用方应填充格式和 Num，然后设置有效 */
		return fc_old;
	}
	else
	{
#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "NEW (%d)", n_NUMCache);
#endif
		Assert(NUMCache[n_NUMCache] == NULL);
		NUMCache[n_NUMCache] = fc_ent = (NUMCacheEntry *)
			MemoryContextAllocZero(TopMemoryContext, sizeof(NUMCacheEntry));
		fc_ent->valid = false;
		strlcpy(fc_ent->str, fc_str, NUM_CACHE_SIZE + 1);
		fc_ent->age = (++NUMCounter);
		/* 调用方应填充格式和 Num，然后设置有效 */
		++n_NUMCache;
		return fc_ent;
	}
}

/* 查找与给定格式图片匹配的现有 NUMCacheEntry */
static NUMCacheEntry * fc_NUM_cache_search(const char *fc_str)
{
	/* 确保我们可以向下推进 NUMCounter */
	fc_NUM_prevent_counter_overflow();

	for (int fc_i = 0; fc_i < n_NUMCache; fc_i++)
	{
		NUMCacheEntry *fc_ent = NUMCache[fc_i];

		if (fc_ent->valid && strcmp(fc_ent->str, fc_str) == 0)
		{
			fc_ent->age = (++NUMCounter);
			return fc_ent;
		}
	}

	return NULL;
}

/* 为给定格式图片查找或创建 NUMCacheEntry */
static NUMCacheEntry * fc_NUM_cache_fetch(const char *fc_str)
{
	NUMCacheEntry *fc_ent;

	if ((fc_ent = fc_NUM_cache_search(fc_str)) == NULL)
	{
		/*
		 * 不在缓存中，必须运行解析器并将新的格式图片保存到
		 * 缓存中。在解析成功之前，不要将缓存条目标记为有效。
		 */
		fc_ent = fc_NUM_cache_getnew(fc_str);

		zeroize_NUM(&fc_ent->Num);

		fc_parse_format(fc_ent->format, fc_str, NUM_keywords,
					 NULL, NUM_index, NUM_FLAG, &fc_ent->Num);

		fc_ent->valid = true;
	}
	return fc_ent;
}

/* ----------
 * NUM to_char 版本的缓存例程
 * ----------
 */
static FormatNode *
fc_NUM_cache(int fc_len, NUMDesc *fc_Num, text *fc_pars_str, bool *fc_shouldFree)
{
	FormatNode *fc_format = NULL;
	char	   *fc_str;

	fc_str = text_to_cstring(fc_pars_str);

	if (fc_len > NUM_CACHE_SIZE)
	{
		/*
		 * 如果格式图片大于静态缓存，则分配新内存
		 * 并且始终调用解析器，不使用缓存
		 */
		fc_format = (FormatNode *) palloc((fc_len + 1) * sizeof(FormatNode));

		*fc_shouldFree = true;

		zeroize_NUM(fc_Num);

		fc_parse_format(fc_format, fc_str, NUM_keywords,
					 NULL, NUM_index, NUM_FLAG, fc_Num);
	}
	else
	{
		/*
		 * 使用缓存缓冲区
		 */
		NUMCacheEntry *fc_ent = fc_NUM_cache_fetch(fc_str);

		*fc_shouldFree = false;

		fc_format = fc_ent->format;

		/*
		 * 将缓存复制到使用的结构中
		 */
		fc_Num->flag = fc_ent->Num.flag;
		fc_Num->lsign = fc_ent->Num.lsign;
		fc_Num->pre = fc_ent->Num.pre;
		fc_Num->post = fc_ent->Num.post;
		fc_Num->pre_lsign_num = fc_ent->Num.pre_lsign_num;
		fc_Num->need_locale = fc_ent->Num.need_locale;
		fc_Num->multi = fc_ent->Num.multi;
		fc_Num->zero_start = fc_ent->Num.zero_start;
		fc_Num->zero_end = fc_ent->Num.zero_end;
	}

#ifdef DEBUG_TO_FROM_CHAR
	/* dump_node(format, len); */
	fc_dump_index(NUM_keywords, NUM_index);
#endif

	pfree(fc_str);
	return fc_format;
}


static char * fc_int_to_roman(int fc_number)
{
	int			fc_len,
				fc_num;
	char	   *fc_p,
			   *fc_result,
				fc_numstr[12];

	fc_result = (char *) palloc(16);
	*fc_result = '\0';

	if (fc_number > 3999 || fc_number < 1)
	{
		fc_fill_str(fc_result, '#', 15);
		return fc_result;
	}
	fc_len = snprintf(fc_numstr, sizeof(fc_numstr), "%d", fc_number);

	for (fc_p = fc_numstr; *fc_p != '\0'; fc_p++, --fc_len)
	{
		fc_num = *fc_p - ('0' + 1);
		if (fc_num < 0)
			continue;

		if (fc_len > 3)
		{
			while (fc_num-- != -1)
				strcat(fc_result, "M");
		}
		else
		{
			if (fc_len == 3)
				strcat(fc_result, rm100[fc_num]);
			else if (fc_len == 2)
				strcat(fc_result, rm10[fc_num]);
			else if (fc_len == 1)
				strcat(fc_result, rm1[fc_num]);
		}
	}
	return fc_result;
}



/* ----------
 * 区域设置
 * ----------
 */
static void fc_NUM_prepare_locale(NUMProc *fc_Np)
{
	if (fc_Np->Num->need_locale)
	{
		struct lconv *fc_lconv;

		/*
		 * 获取区域设置
		 */
		fc_lconv = PGLC_localeconv();

		/*
		 * 正数 / 负数符号
		 */
		if (fc_lconv->negative_sign && *fc_lconv->negative_sign)
			fc_Np->L_negative_sign = fc_lconv->negative_sign;
		else
			fc_Np->L_negative_sign = "-";

		if (fc_lconv->positive_sign && *fc_lconv->positive_sign)
			fc_Np->L_positive_sign = fc_lconv->positive_sign;
		else
			fc_Np->L_positive_sign = "+";

		/*
		 * 数字小数点
		 */
		if (fc_lconv->decimal_point && *fc_lconv->decimal_point)
			fc_Np->decimal = fc_lconv->decimal_point;

		else
			fc_Np->decimal = ".";

		if (!IS_LDECIMAL(fc_Np->Num))
			fc_Np->decimal = ".";

		/*
		 * 数字千位分隔符
		 *
		 * 一些区域设置（例如损坏的 glibc pt_BR）将逗号用于小数，
		 * 但千位分隔符为 ""，因此我们也设置千位分隔符。
		 * http://archives.postgresql.org/pgsql-hackers/2007-11/msg00772.php
		 */
		if (fc_lconv->thousands_sep && *fc_lconv->thousands_sep)
			fc_Np->L_thousands_sep = fc_lconv->thousands_sep;
		/* 确保千位分隔符与小数点符号不匹配。 */
		else if (strcmp(fc_Np->decimal, ",") != 0)
			fc_Np->L_thousands_sep = ",";
		else
			fc_Np->L_thousands_sep = ".";

		/*
		 * 货币符号
		 */
		if (fc_lconv->currency_symbol && *fc_lconv->currency_symbol)
			fc_Np->L_currency_symbol = fc_lconv->currency_symbol;
		else
			fc_Np->L_currency_symbol = " ";
	}
	else
	{
		/*
		 * 默认值
		 */
		fc_Np->L_negative_sign = "-";
		fc_Np->L_positive_sign = "+";
		fc_Np->decimal = ".";

		fc_Np->L_thousands_sep = ",";
		fc_Np->L_currency_symbol = " ";
	}
}

/* ----------
 * 返回小数点后最后一个相关数字的指针
 *	12.0500 --> 最后一个相关的是 '5'
 *	12.0000 --> 最后一个相关的是 '.'
 * 如果没有小数点，则返回 NULL（这将导致与未指定 FM
 * 时相同的行为）。
 * ----------
 */
static char * get_last_relevant_decnum(char *fc_num)
{
	char	   *fc_result,
			   *fc_p = strchr(fc_num, '.');

#ifdef DEBUG_TO_FROM_CHAR
	elog(DEBUG_elog_output, "get_last_relevant_decnum()");
#endif

	if (!fc_p)
		return NULL;

	fc_result = fc_p;

	while (*(++fc_p))
	{
		if (*fc_p != '0')
			fc_result = fc_p;
	}

	return fc_result;
}

/*
 * 这些宏在 NUM_processor() 及其子例程中使用。
 * OVERLOAD_TEST：如果我们已到达输入字符串的末尾，则为真
 * AMOUNT_TEST(s)：如果字符串中至少剩余 s 字节，则为真
 */
#define OVERLOAD_TEST	(fc_Np->inout_p >= fc_Np->inout + fc_input_len)
#define AMOUNT_TEST(s)	(fc_Np->inout_p <= fc_Np->inout + (fc_input_len - (s)))

/* ----------
 * TO_NUMBER() 的数字提取
 * ----------
 */
static void fc_NUM_numpart_from_char(NUMProc *fc_Np, int fc_id, int fc_input_len)
{
	bool		fc_isread = false;

#ifdef DEBUG_TO_FROM_CHAR
	elog(DEBUG_elog_output, " --- scan start --- id=%s",
		 (fc_id == NUM_0 || fc_id == NUM_9) ? "NUM_0/9" : fc_id == NUM_DEC ? "NUM_DEC" : "???");
#endif

	if (OVERLOAD_TEST)
		return;

	if (*fc_Np->inout_p == ' ')
		fc_Np->inout_p++;

	if (OVERLOAD_TEST)
		return;

	/*
	 * 读取数字前的符号
	 */
	if (*fc_Np->number == ' ' && (fc_id == NUM_0 || fc_id == NUM_9) &&
		(fc_Np->read_pre + fc_Np->read_post) == 0)
	{
#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "Try read sign (%c), locale positive: %s, negative: %s",
			 *fc_Np->inout_p, fc_Np->L_positive_sign, fc_Np->L_negative_sign);
#endif

		/*
		 * 本地符号
		 */
		if (IS_LSIGN(fc_Np->Num) && fc_Np->Num->lsign == NUM_LSIGN_PRE)
		{
			int			fc_x = 0;

#ifdef DEBUG_TO_FROM_CHAR
			elog(DEBUG_elog_output, "Try read locale pre-sign (%c)", *fc_Np->inout_p);
#endif
			if ((fc_x = strlen(fc_Np->L_negative_sign)) &&
				AMOUNT_TEST(fc_x) &&
				strncmp(fc_Np->inout_p, fc_Np->L_negative_sign, fc_x) == 0)
			{
				fc_Np->inout_p += fc_x;
				*fc_Np->number = '-';
			}
			else if ((fc_x = strlen(fc_Np->L_positive_sign)) &&
					 AMOUNT_TEST(fc_x) &&
					 strncmp(fc_Np->inout_p, fc_Np->L_positive_sign, fc_x) == 0)
			{
				fc_Np->inout_p += fc_x;
				*fc_Np->number = '+';
			}
		}
		else
		{
#ifdef DEBUG_TO_FROM_CHAR
			elog(DEBUG_elog_output, "Try read simple sign (%c)", *fc_Np->inout_p);
#endif

			/*
			 * 简单的 + - < >
			 */
			if (*fc_Np->inout_p == '-' || (IS_BRACKET(fc_Np->Num) &&
										*fc_Np->inout_p == '<'))
			{
				*fc_Np->number = '-';	/* set - */
				fc_Np->inout_p++;
			}
			else if (*fc_Np->inout_p == '+')
			{
				*fc_Np->number = '+';	/* set + */
				fc_Np->inout_p++;
			}
		}
	}

	if (OVERLOAD_TEST)
		return;

#ifdef DEBUG_TO_FROM_CHAR
	elog(DEBUG_elog_output, "Scan for numbers (%c), current number: '%s'", *fc_Np->inout_p, fc_Np->number);
#endif

	/*
	 * 读取数字或小数点
	 */
	if (isdigit((unsigned char) *fc_Np->inout_p))
	{
		if (fc_Np->read_dec && fc_Np->read_post == fc_Np->Num->post)
			return;

		*fc_Np->number_p = *fc_Np->inout_p;
		fc_Np->number_p++;

		if (fc_Np->read_dec)
			fc_Np->read_post++;
		else
			fc_Np->read_pre++;

		fc_isread = true;

#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "Read digit (%c)", *fc_Np->inout_p);
#endif
	}
	else if (IS_DECIMAL(fc_Np->Num) && fc_Np->read_dec == false)
	{
		/*
		 * 我们这里不需要明确测试 IS_LDECIMAL(Np->Num)，因为
		 * 如果没有 D 格式标记，Np->decimal 总是就是 "."。
		 * 所以我们只需无条件匹配 Np->decimal。
		 */
		int			fc_x = strlen(fc_Np->decimal);

#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "Try read decimal point (%c)",
			 *fc_Np->inout_p);
#endif
		if (fc_x && AMOUNT_TEST(fc_x) && strncmp(fc_Np->inout_p, fc_Np->decimal, fc_x) == 0)
		{
			fc_Np->inout_p += fc_x - 1;
			*fc_Np->number_p = '.';
			fc_Np->number_p++;
			fc_Np->read_dec = true;
			fc_isread = true;
		}
	}

	if (OVERLOAD_TEST)
		return;

	/*
	 * 读取“最后”一个数字后面的符号
	 *
	 * 我们需要符号检测，因为确定后符号的确切位置是
	 * 困难的：
	 *
	 * FM9999.9999999S	   -> 123.001- 9.9S			   -> .5- FM9.999999MI ->
	 * 5.01-
	 */
	if (*fc_Np->number == ' ' && fc_Np->read_pre + fc_Np->read_post > 0)
	{
		/*
		 * 本地符号 (NUM_S) 总是锚定在最后一个数字之后，如果： -
		 * 预期有本地符号 - 最后读取的字符是 NUM_0/9 或 NUM_DEC - 并且
		 * 下一个字符不是数字
		 */
		if (IS_LSIGN(fc_Np->Num) && fc_isread &&
			(fc_Np->inout_p + 1) < fc_Np->inout + fc_input_len &&
			!isdigit((unsigned char) *(fc_Np->inout_p + 1)))
		{
			int			fc_x;
			char	   *fc_tmp = fc_Np->inout_p++;

#ifdef DEBUG_TO_FROM_CHAR
			elog(DEBUG_elog_output, "Try read locale post-sign (%c)", *fc_Np->inout_p);
#endif
			if ((fc_x = strlen(fc_Np->L_negative_sign)) &&
				AMOUNT_TEST(fc_x) &&
				strncmp(fc_Np->inout_p, fc_Np->L_negative_sign, fc_x) == 0)
			{
				fc_Np->inout_p += fc_x - 1;	/* -1 .. NUM_processor()执行 inout_p++ */
				*fc_Np->number = '-';
			}
			else if ((fc_x = strlen(fc_Np->L_positive_sign)) &&
					 AMOUNT_TEST(fc_x) &&
					 strncmp(fc_Np->inout_p, fc_Np->L_positive_sign, fc_x) == 0)
			{
				fc_Np->inout_p += fc_x - 1;	/* -1 .. NUM_processor()执行 inout_p++ */
				*fc_Np->number = '+';
			}
			if (*fc_Np->number == ' ')
				/* 未读取符号 */
				fc_Np->inout_p = fc_tmp;
		}

		/*
		 * 尝试读取非本地符号，仅在格式不准确时发生
		 * 并且我们无法确定 MI/PL/SG 的符号位置，示例：
		 *
		 * FM9.999999MI			   -> 5.01-
		 *
		 * 如果 (.... && IS_LSIGN(Np->Num)==false) 防止读取错误格式
		 * 像 to_number('1 -', '9S')，其中符号未锚定在最后
		 * 数字。
		 */
		else if (fc_isread == false && IS_LSIGN(fc_Np->Num) == false &&
				 (IS_PLUS(fc_Np->Num) || IS_MINUS(fc_Np->Num)))
		{
#ifdef DEBUG_TO_FROM_CHAR
			elog(DEBUG_elog_output, "Try read simple post-sign (%c)", *fc_Np->inout_p);
#endif

			/*
			 * 简单 + -
			 */
			if (*fc_Np->inout_p == '-' || *fc_Np->inout_p == '+')
				/* NUM_processor() 执行 inout_p++ */
				*fc_Np->number = *fc_Np->inout_p;
		}
	}
}

#define IS_PREDEC_SPACE(_n) \
		(IS_ZERO((_n)->Num)==false && \
		 (_n)->number == (_n)->number_p && \
		 *(_n)->number == '0' && \
				 (_n)->Num->post != 0)

/* ----------
 * 将数字或符号添加到数字字符串
 * ----------
 */
static void fc_NUM_numpart_to_char(NUMProc *fc_Np, int fc_id)
{
	int			fc_end;

	if (IS_ROMAN(fc_Np->Num))
		return;

	/* 注意：在此 elog() 输出中未在 'inout' 中设置 '\0' */

#ifdef DEBUG_TO_FROM_CHAR

	/*
	 * Np->num_curr 是格式图中当前项的数字，它不是
	 * inout 中的当前位置！
	 */
	elog(DEBUG_elog_output,
		 "SIGN_WROTE: %d, CURRENT: %d, NUMBER_P: \"%s\", INOUT: \"%s\"",
		 fc_Np->sign_wrote,
		 fc_Np->num_curr,
		 fc_Np->number_p,
		 fc_Np->inout);
#endif
	fc_Np->num_in = false;

	/*
	 * 如果实数将写入输出，则写入符号 注意：IS_PREDEC_SPACE()
	 * 处理 "9.9" --> " .1"
	 */
	if (fc_Np->sign_wrote == false &&
		(fc_Np->num_curr >= fc_Np->out_pre_spaces || (IS_ZERO(fc_Np->Num) && fc_Np->Num->zero_start == fc_Np->num_curr)) &&
		(IS_PREDEC_SPACE(fc_Np) == false || (fc_Np->last_relevant && *fc_Np->last_relevant == '.')))
	{
		if (IS_LSIGN(fc_Np->Num))
		{
			if (fc_Np->Num->lsign == NUM_LSIGN_PRE)
			{
				if (fc_Np->sign == '-')
					strcpy(fc_Np->inout_p, fc_Np->L_negative_sign);
				else
					strcpy(fc_Np->inout_p, fc_Np->L_positive_sign);
				fc_Np->inout_p += strlen(fc_Np->inout_p);
				fc_Np->sign_wrote = true;
			}
		}
		else if (IS_BRACKET(fc_Np->Num))
		{
			*fc_Np->inout_p = fc_Np->sign == '+' ? ' ' : '<';
			++fc_Np->inout_p;
			fc_Np->sign_wrote = true;
		}
		else if (fc_Np->sign == '+')
		{
			if (!IS_FILLMODE(fc_Np->Num))
			{
				*fc_Np->inout_p = ' '; /* Write + */
				++fc_Np->inout_p;
			}
			fc_Np->sign_wrote = true;
		}
		else if (fc_Np->sign == '-')
		{						/* Write - */
			*fc_Np->inout_p = '-';
			++fc_Np->inout_p;
			fc_Np->sign_wrote = true;
		}
	}


	/*
	 * 数字 / FM / 零 / 小数点
	 */
	if (fc_id == NUM_9 || fc_id == NUM_0 || fc_id == NUM_D || fc_id == NUM_DEC)
	{
		if (fc_Np->num_curr < fc_Np->out_pre_spaces &&
			(fc_Np->Num->zero_start > fc_Np->num_curr || !IS_ZERO(fc_Np->Num)))
		{
			/*
			 * 写入空格
			 */
			if (!IS_FILLMODE(fc_Np->Num))
			{
				*fc_Np->inout_p = ' '; /* 写入 ' ' */
				++fc_Np->inout_p;
			}
		}
		else if (IS_ZERO(fc_Np->Num) &&
				 fc_Np->num_curr < fc_Np->out_pre_spaces &&
				 fc_Np->Num->zero_start <= fc_Np->num_curr)
		{
			/*
			 * 写入零
			 */
			*fc_Np->inout_p = '0'; /* 写入 '0' */
			++fc_Np->inout_p;
			fc_Np->num_in = true;
		}
		else
		{
			/*
			 * 写入小数点
			 */
			if (*fc_Np->number_p == '.')
			{
				if (!fc_Np->last_relevant || *fc_Np->last_relevant != '.')
				{
					strcpy(fc_Np->inout_p, fc_Np->decimal);	/* 写入 DEC/D */
					fc_Np->inout_p += strlen(fc_Np->inout_p);
				}

				/*
				 * Ora 'n' -- FM9.9 --> 'n.'
				 */
				else if (IS_FILLMODE(fc_Np->Num) &&
						 fc_Np->last_relevant && *fc_Np->last_relevant == '.')
				{
					strcpy(fc_Np->inout_p, fc_Np->decimal);	/* 写入 DEC/D */
					fc_Np->inout_p += strlen(fc_Np->inout_p);
				}
			}
			else
			{
				/*
				 * 写入数字
				 */
				if (fc_Np->last_relevant && fc_Np->number_p > fc_Np->last_relevant &&
					fc_id != NUM_0)
					;

				/*
				 * '0.1' -- 9.9 --> '  .1'
				 */
				else if (IS_PREDEC_SPACE(fc_Np))
				{
					if (!IS_FILLMODE(fc_Np->Num))
					{
						*fc_Np->inout_p = ' ';
						++fc_Np->inout_p;
					}

					/*
					 * '0' -- FM9.9 --> '0.'
					 */
					else if (fc_Np->last_relevant && *fc_Np->last_relevant == '.')
					{
						*fc_Np->inout_p = '0';
						++fc_Np->inout_p;
					}
				}
				else
				{
					*fc_Np->inout_p = *fc_Np->number_p;	/* 写入数字 */
					++fc_Np->inout_p;
					fc_Np->num_in = true;
				}
			}
			/* 不要超过字符串长度 */
			if (*fc_Np->number_p)
				++fc_Np->number_p;
		}

		fc_end = fc_Np->num_count + (fc_Np->out_pre_spaces ? 1 : 0) + (IS_DECIMAL(fc_Np->Num) ? 1 : 0);

		if (fc_Np->last_relevant && fc_Np->last_relevant == fc_Np->number_p)
			fc_end = fc_Np->num_curr;

		if (fc_Np->num_curr + 1 == fc_end)
		{
			if (fc_Np->sign_wrote == true && IS_BRACKET(fc_Np->Num))
			{
				*fc_Np->inout_p = fc_Np->sign == '+' ? ' ' : '>';
				++fc_Np->inout_p;
			}
			else if (IS_LSIGN(fc_Np->Num) && fc_Np->Num->lsign == NUM_LSIGN_POST)
			{
				if (fc_Np->sign == '-')
					strcpy(fc_Np->inout_p, fc_Np->L_negative_sign);
				else
					strcpy(fc_Np->inout_p, fc_Np->L_positive_sign);
				fc_Np->inout_p += strlen(fc_Np->inout_p);
			}
		}
	}

	++fc_Np->num_curr;
}

/*
 * 跳过“n”个输入字符，但仅在它们不是数字数据时
 */
static void fc_NUM_eat_non_data_chars(NUMProc *fc_Np, int fc_n, int fc_input_len)
{
	while (fc_n-- > 0)
	{
		if (OVERLOAD_TEST)
			break;				/* 输入结束 */
		if (strchr("0123456789.,+-", *fc_Np->inout_p) != NULL)
			break;				/* 这是一个数据字符 */
		fc_Np->inout_p += pg_mblen(fc_Np->inout_p);
	}
}

static char * fc_NUM_processor(FormatNode *fc_node, NUMDesc *fc_Num, char *fc_inout,
			  char *fc_number, int fc_input_len, int fc_to_char_out_pre_spaces,
			  int fc_sign, bool fc_is_to_char, Oid fc_collid)
{
	FormatNode *fc_n;
	NUMProc		fc__Np,
			   *fc_Np = &fc__Np;
	const char *fc_pattern;
	int			fc_pattern_len;

	MemSet(fc_Np, 0, sizeof(NUMProc));

	fc_Np->Num = fc_Num;
	fc_Np->is_to_char = fc_is_to_char;
	fc_Np->number = fc_number;
	fc_Np->inout = fc_inout;
	fc_Np->last_relevant = NULL;
	fc_Np->read_post = 0;
	fc_Np->read_pre = 0;
	fc_Np->read_dec = false;

	if (fc_Np->Num->zero_start)
		--fc_Np->Num->zero_start;

	if (IS_EEEE(fc_Np->Num))
	{
		if (!fc_Np->is_to_char)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("\"EEEE\" not supported for input")));
		return strcpy(fc_inout, fc_number);
	}

	/* 
	 * 罗马校正 
	 */
	if (IS_ROMAN(fc_Np->Num))
	{
		if (!fc_Np->is_to_char)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("\"RN\" not supported for input")));

		fc_Np->Num->lsign = fc_Np->Num->pre_lsign_num = fc_Np->Num->post =
			fc_Np->Num->pre = fc_Np->out_pre_spaces = fc_Np->sign = 0;

		if (IS_FILLMODE(fc_Np->Num))
		{
			fc_Np->Num->flag = 0;
			fc_Np->Num->flag |= NUM_F_FILLMODE;
		}
		else
			fc_Np->Num->flag = 0;
		fc_Np->Num->flag |= NUM_F_ROMAN;
	}

	/* 
	 * 符号 
	 */
	if (fc_is_to_char)
	{
		fc_Np->sign = fc_sign;

		/* MI/PL/SG - 直接写符号本身，而不是数字 */
		if (IS_PLUS(fc_Np->Num) || IS_MINUS(fc_Np->Num))
		{
			if (IS_PLUS(fc_Np->Num) && IS_MINUS(fc_Np->Num) == false)
				fc_Np->sign_wrote = false; /* 需要符号 */
			else
				fc_Np->sign_wrote = true;	/* 不需要符号 */
		}
		else
		{
			if (fc_Np->sign != '-')
			{
				if (IS_BRACKET(fc_Np->Num) && IS_FILLMODE(fc_Np->Num))
					fc_Np->Num->flag &= ~NUM_F_BRACKET;
				if (IS_MINUS(fc_Np->Num))
					fc_Np->Num->flag &= ~NUM_F_MINUS;
			}
			else if (fc_Np->sign != '+' && IS_PLUS(fc_Np->Num))
				fc_Np->Num->flag &= ~NUM_F_PLUS;

			if (fc_Np->sign == '+' && IS_FILLMODE(fc_Np->Num) && IS_LSIGN(fc_Np->Num) == false)
				fc_Np->sign_wrote = true;	/* 不需要符号 */
			else
				fc_Np->sign_wrote = false; /* 需要符号 */

			if (fc_Np->Num->lsign == NUM_LSIGN_PRE && fc_Np->Num->pre == fc_Np->Num->pre_lsign_num)
				fc_Np->Num->lsign = NUM_LSIGN_POST;
		}
	}
	else
		fc_Np->sign = false;

	/* 
	 * 计数 
	 */
	fc_Np->num_count = fc_Np->Num->post + fc_Np->Num->pre - 1;

	if (fc_is_to_char)
	{
		fc_Np->out_pre_spaces = fc_to_char_out_pre_spaces;

		if (IS_FILLMODE(fc_Np->Num) && IS_DECIMAL(fc_Np->Num))
		{
			fc_Np->last_relevant = get_last_relevant_decnum(fc_Np->number);

			/* 
			 * 如果存在任何 '0' 指定符号，请确保我们不删除 
			 * 这些数字。但不要将 last_relevant 移动超过 Np->number 字符串的最后一个字符，这会导致 
			 * 如果由于精度限制而缩短数字，这就很危险。
			 */
			if (fc_Np->last_relevant && fc_Np->Num->zero_end > fc_Np->out_pre_spaces)
			{
				int			fc_last_zero_pos;
				char	   *fc_last_zero;

				/* 注意，Np->number 这里不能是零长度 */
				fc_last_zero_pos = strlen(fc_Np->number) - 1;
				fc_last_zero_pos = Min(fc_last_zero_pos,
									fc_Np->Num->zero_end - fc_Np->out_pre_spaces);
				fc_last_zero = fc_Np->number + fc_last_zero_pos;
				if (fc_Np->last_relevant < fc_last_zero)
					fc_Np->last_relevant = fc_last_zero;
			}
		}

		if (fc_Np->sign_wrote == false && fc_Np->out_pre_spaces == 0)
			++fc_Np->num_count;
	}
	else
	{
		fc_Np->out_pre_spaces = 0;
		*fc_Np->number = ' ';		/* 符号空间 */
		*(fc_Np->number + 1) = '\0';
	}

	fc_Np->num_in = 0;
	fc_Np->num_curr = 0;

#ifdef DEBUG_TO_FROM_CHAR
	elog(DEBUG_elog_output,
		 "\n\tSIGN: '%c'\n\tNUM: '%s'\n\tPRE: %d\n\tPOST: %d\n\tNUM_COUNT: %d\n\tNUM_PRE: %d\n\tSIGN_WROTE: %s\n\tZERO: %s\n\tZERO_START: %d\n\tZERO_END: %d\n\tLAST_RELEVANT: %s\n\tBRACKET: %s\n\tPLUS: %s\n\tMINUS: %s\n\tFILLMODE: %s\n\tROMAN: %s\n\tEEEE: %s",
		 fc_Np->sign,
		 fc_Np->number,
		 fc_Np->Num->pre,
		 fc_Np->Num->post,
		 fc_Np->num_count,
		 fc_Np->out_pre_spaces,
		 fc_Np->sign_wrote ? "Yes" : "No",
		 IS_ZERO(fc_Np->Num) ? "Yes" : "No",
		 fc_Np->Num->zero_start,
		 fc_Np->Num->zero_end,
		 fc_Np->last_relevant ? fc_Np->last_relevant : "<not set>",
		 IS_BRACKET(fc_Np->Num) ? "Yes" : "No",
		 IS_PLUS(fc_Np->Num) ? "Yes" : "No",
		 IS_MINUS(fc_Np->Num) ? "Yes" : "No",
		 IS_FILLMODE(fc_Np->Num) ? "Yes" : "No",
		 IS_ROMAN(fc_Np->Num) ? "Yes" : "No",
		 IS_EEEE(fc_Np->Num) ? "Yes" : "No"
		);
#endif

	/* 
	 * 区域 
	 */
	fc_NUM_prepare_locale(fc_Np);

	/* 
	 * 处理器直接周期 
	 */
	if (fc_Np->is_to_char)
		fc_Np->number_p = fc_Np->number;
	else
		fc_Np->number_p = fc_Np->number + 1;	/* 第一个字符是符号的空格 */

	for (fc_n = fc_node, fc_Np->inout_p = fc_Np->inout; fc_n->type != NODE_TYPE_END; fc_n++)
	{
		if (!fc_Np->is_to_char)
		{
			/* 
			 * 检查至少有一个字节剩余待扫描。（在下面的动作中，如果我们想读取比 
			 * 更多的字节，必须使用 AMOUNT_TEST。）
			 */
			if (OVERLOAD_TEST)
				break;
		}

		/* 
		 * 格式图像动作 
		 */
		if (fc_n->type == NODE_TYPE_ACTION)
		{
			/* 
			 * 创建/读取数字/零/空白/符号/特殊情况
			 *
			 * 'NUM_S' 注意：区域符号与数字锚定，当我们处理第一个或最后一个数字时 
			 * （NUM_0/NUM_9）时我们会读取/写入它。这就是为什么在 switch() 中缺少 NUM_S。
			 *
			 * 注意循环底部的 "Np->inout_p++"。这就是为什么大多数动作在 
			 * 你可能期望的基础上减少一个。对于我们不希望发生该增量的情况， 
			 * switch 情况以 "continue" 而不是 "break" 结束。
			 */
			switch (fc_n->key->id)
			{
				case NUM_9:
				case NUM_0:
				case NUM_DEC:
				case NUM_D:
					if (fc_Np->is_to_char)
					{
						fc_NUM_numpart_to_char(fc_Np, fc_n->key->id);
						continue;	/* for() */
					}
					else
					{
						fc_NUM_numpart_from_char(fc_Np, fc_n->key->id, fc_input_len);
						break;	/* switch() 情况： */
					}

				case NUM_COMMA:
					if (fc_Np->is_to_char)
					{
						if (!fc_Np->num_in)
						{
							if (IS_FILLMODE(fc_Np->Num))
								continue;
							else
								*fc_Np->inout_p = ' ';
						}
						else
							*fc_Np->inout_p = ',';
					}
					else
					{
						if (!fc_Np->num_in)
						{
							if (IS_FILLMODE(fc_Np->Num))
								continue;
						}
						if (*fc_Np->inout_p != ',')
							continue;
					}
					break;

				case NUM_G:
					fc_pattern = fc_Np->L_thousands_sep;
					fc_pattern_len = strlen(fc_pattern);
					if (fc_Np->is_to_char)
					{
						if (!fc_Np->num_in)
						{
							if (IS_FILLMODE(fc_Np->Num))
								continue;
							else
							{
								/* 以防有 MB 字符 */
								fc_pattern_len = pg_mbstrlen(fc_pattern);
								memset(fc_Np->inout_p, ' ', fc_pattern_len);
								fc_Np->inout_p += fc_pattern_len - 1;
							}
						}
						else
						{
							strcpy(fc_Np->inout_p, fc_pattern);
							fc_Np->inout_p += fc_pattern_len - 1;
						}
					}
					else
					{
						if (!fc_Np->num_in)
						{
							if (IS_FILLMODE(fc_Np->Num))
								continue;
						}

						/* 
						 * 因为 L_thousands_sep 通常包含数据 
						 * 字符（即 '.' 或 ','），我们不能在这里使用 
						 * NUM_eat_non_data_chars。相反，仅在 
						 * 输入与 L_thousands_sep 匹配时跳过。
						 */
						if (AMOUNT_TEST(fc_pattern_len) &&
							strncmp(fc_Np->inout_p, fc_pattern, fc_pattern_len) == 0)
							fc_Np->inout_p += fc_pattern_len - 1;
						else
							continue;
					}
					break;

				case NUM_L:
					fc_pattern = fc_Np->L_currency_symbol;
					if (fc_Np->is_to_char)
					{
						strcpy(fc_Np->inout_p, fc_pattern);
						fc_Np->inout_p += strlen(fc_pattern) - 1;
					}
					else
					{
						fc_NUM_eat_non_data_chars(fc_Np, pg_mbstrlen(fc_pattern), fc_input_len);
						continue;
					}
					break;

				case NUM_RN:
					if (IS_FILLMODE(fc_Np->Num))
					{
						strcpy(fc_Np->inout_p, fc_Np->number_p);
						fc_Np->inout_p += strlen(fc_Np->inout_p) - 1;
					}
					else
					{
						sprintf(fc_Np->inout_p, "%15s", fc_Np->number_p);
						fc_Np->inout_p += strlen(fc_Np->inout_p) - 1;
					}
					break;

				case NUM_rn:
					if (IS_FILLMODE(fc_Np->Num))
					{
						strcpy(fc_Np->inout_p, fc_asc_tolower_z(fc_Np->number_p));
						fc_Np->inout_p += strlen(fc_Np->inout_p) - 1;
					}
					else
					{
						sprintf(fc_Np->inout_p, "%15s", fc_asc_tolower_z(fc_Np->number_p));
						fc_Np->inout_p += strlen(fc_Np->inout_p) - 1;
					}
					break;

				case NUM_th:
					if (IS_ROMAN(fc_Np->Num) || *fc_Np->number == '#' ||
						fc_Np->sign == '-' || IS_DECIMAL(fc_Np->Num))
						continue;

					if (fc_Np->is_to_char)
					{
						strcpy(fc_Np->inout_p, get_th(fc_Np->number, TH_LOWER));
						fc_Np->inout_p += 1;
					}
					else
					{
						/* 所有 'th' 的变体占用 2 个字符 */
						fc_NUM_eat_non_data_chars(fc_Np, 2, fc_input_len);
						continue;
					}
					break;

				case NUM_TH:
					if (IS_ROMAN(fc_Np->Num) || *fc_Np->number == '#' ||
						fc_Np->sign == '-' || IS_DECIMAL(fc_Np->Num))
						continue;

					if (fc_Np->is_to_char)
					{
						strcpy(fc_Np->inout_p, get_th(fc_Np->number, TH_UPPER));
						fc_Np->inout_p += 1;
					}
					else
					{
						/* 所有 'TH' 的变体占用 2 个字符 */
						fc_NUM_eat_non_data_chars(fc_Np, 2, fc_input_len);
						continue;
					}
					break;

				case NUM_MI:
					if (fc_Np->is_to_char)
					{
						if (fc_Np->sign == '-')
							*fc_Np->inout_p = '-';
						else if (IS_FILLMODE(fc_Np->Num))
							continue;
						else
							*fc_Np->inout_p = ' ';
					}
					else
					{
						if (*fc_Np->inout_p == '-')
							*fc_Np->number = '-';
						else
						{
							fc_NUM_eat_non_data_chars(fc_Np, 1, fc_input_len);
							continue;
						}
					}
					break;

				case NUM_PL:
					if (fc_Np->is_to_char)
					{
						if (fc_Np->sign == '+')
							*fc_Np->inout_p = '+';
						else if (IS_FILLMODE(fc_Np->Num))
							continue;
						else
							*fc_Np->inout_p = ' ';
					}
					else
					{
						if (*fc_Np->inout_p == '+')
							*fc_Np->number = '+';
						else
						{
							fc_NUM_eat_non_data_chars(fc_Np, 1, fc_input_len);
							continue;
						}
					}
					break;

				case NUM_SG:
					if (fc_Np->is_to_char)
						*fc_Np->inout_p = fc_Np->sign;
					else
					{
						if (*fc_Np->inout_p == '-')
							*fc_Np->number = '-';
						else if (*fc_Np->inout_p == '+')
							*fc_Np->number = '+';
						else
						{
							fc_NUM_eat_non_data_chars(fc_Np, 1, fc_input_len);
							continue;
						}
					}
					break;

				default:
					continue;
					break;
			}
		}
		else
		{
			/* 
			 * 在 TO_CHAR 中，格式中的非模式字符会被复制到 
			 * 输出中。在 TO_NUMBER 中，无论是否与 
			 * 格式字符匹配，我们都会跳过一个输入字符，以应对每个 
			 * 非模式格式字符。
			 */
			if (fc_Np->is_to_char)
			{
				strcpy(fc_Np->inout_p, fc_n->character);
				fc_Np->inout_p += strlen(fc_Np->inout_p);
			}
			else
			{
				fc_Np->inout_p += pg_mblen(fc_Np->inout_p);
			}
			continue;
		}
		fc_Np->inout_p++;
	}

	if (fc_Np->is_to_char)
	{
		*fc_Np->inout_p = '\0';
		return fc_Np->inout;
	}
	else
	{
		if (*(fc_Np->number_p - 1) == '.')
			*(fc_Np->number_p - 1) = '\0';
		else
			*fc_Np->number_p = '\0';

		/* 
		 * 修正 - 小数数字的精度 
		 */
		fc_Np->Num->post = fc_Np->read_post;

#ifdef DEBUG_TO_FROM_CHAR
		elog(DEBUG_elog_output, "TO_NUMBER (number): '%s'", fc_Np->number);
#endif
		return fc_Np->number;
	}
}

/* ----------
 * 宏：NUM 的开始部分 - 适用于所有 NUM 的 to_char 变体
 * (抱歉，我讨厌复制相同的代码 - 宏更好..)
 * ----------
 */
#define NUM_TOCHAR_prepare \
do { \
	int fc_len = VARSIZE_ANY_EXHDR(fc_fmt); \
	if (fc_len <= 0 || fc_len >= (INT_MAX-VARHDRSZ)/NUM_MAX_ITEM_SIZ)		\
		PG_RETURN_TEXT_P(cstring_to_text("")); \
	fc_result	= (text *) palloc0((fc_len * NUM_MAX_ITEM_SIZ) + 1 + VARHDRSZ);	\
	fc_format	= fc_NUM_cache(fc_len, &fc_Num, fc_fmt, &fc_shouldFree);		\
} while (0)

/* ----------
 * 宏：NUM 的结束部分
 * ----------
 */
#define NUM_TOCHAR_finish \
do { \
	int		fc_len; \
									\
	fc_NUM_processor(fc_format, &fc_Num, VARDATA(fc_result), fc_numstr, 0, fc_out_pre_spaces, fc_sign, true, PG_GET_COLLATION()); \
									\
	if (fc_shouldFree)					\
		pfree(fc_format);				\
									\
	/* 								\
	 * 将以空字符终止的结果表示转换为标准文本。 \
	 * 结果通常比所需的要大得多，但 
	 * reallocate 小一点似乎没有什么意义。 \
	 */								\
	fc_len = strlen(VARDATA(fc_result));	\
	SET_VARSIZE(fc_result, fc_len + VARHDRSZ); \
} while (0)

/* -------------------
 * NUMERIC to_number()（将字符串转换为数字）
 * -------------------
 */
Datum numeric_to_number(PG_FUNCTION_ARGS)
{
	text	   *fc_value = PG_GETARG_TEXT_PP(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	Datum		fc_result;
	FormatNode *fc_format;
	char	   *fc_numstr;
	bool		fc_shouldFree;
	int			fc_len = 0;
	int			fc_scale,
				fc_precision;

	fc_len = VARSIZE_ANY_EXHDR(fc_fmt);

	if (fc_len <= 0 || fc_len >= INT_MAX / NUM_MAX_ITEM_SIZ)
		PG_RETURN_NULL();

	fc_format = fc_NUM_cache(fc_len, &fc_Num, fc_fmt, &fc_shouldFree);

	fc_numstr = (char *) palloc((fc_len * NUM_MAX_ITEM_SIZ) + 1);

	fc_NUM_processor(fc_format, &fc_Num, VARDATA_ANY(fc_value), fc_numstr,
				  VARSIZE_ANY_EXHDR(fc_value), 0, 0, false, PG_GET_COLLATION());

	fc_scale = fc_Num.post;
	fc_precision = fc_Num.pre + fc_Num.multi + fc_scale;

	if (fc_shouldFree)
		pfree(fc_format);

	fc_result = DirectFunctionCall3(numeric_in,
								 CStringGetDatum(fc_numstr),
								 ObjectIdGetDatum(InvalidOid),
								 Int32GetDatum(((fc_precision << 16) | fc_scale) + VARHDRSZ));

	if (IS_MULTI(&fc_Num))
	{
		Numeric		fc_x;
		Numeric		fc_a = int64_to_numeric(10);
		Numeric		fc_b = int64_to_numeric(-fc_Num.multi);

		fc_x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
												NumericGetDatum(fc_a),
												NumericGetDatum(fc_b)));
		fc_result = DirectFunctionCall2(numeric_mul,
									 fc_result,
									 NumericGetDatum(fc_x));
	}

	pfree(fc_numstr);
	return fc_result;
}

/* ------------------
 * NUMERIC to_char()
 * ------------------
 */
Datum numeric_to_char(PG_FUNCTION_ARGS)
{
	Numeric		fc_value = PG_GETARG_NUMERIC(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	FormatNode *fc_format;
	text	   *fc_result;
	bool		fc_shouldFree;
	int			fc_out_pre_spaces = 0,
				fc_sign = 0;
	char	   *fc_numstr,
			   *fc_orgnum,
			   *fc_p;
	Numeric		fc_x;

	NUM_TOCHAR_prepare;

	/* 
	 * 根据 DateType 依赖的部分（数字） 
	 */
	if (IS_ROMAN(&fc_Num))
	{
		fc_x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
												NumericGetDatum(fc_value),
												Int32GetDatum(0)));
		fc_numstr =
			fc_int_to_roman(DatumGetInt32(DirectFunctionCall1(numeric_int4,
														   NumericGetDatum(fc_x))));
	}
	else if (IS_EEEE(&fc_Num))
	{
		fc_orgnum = numeric_out_sci(fc_value, fc_Num.post);

		/*
		 * numeric_out_sci() 不会为正数发出符号。
		 * 在这种情况下，我们需要添加一个空格，以便正负数对齐。
		 * 还必须检查 NaN/无穷大情况，我们的处理方式与 float8_to_char 相同。
		 */
		if (strcmp(fc_orgnum, "NaN") == 0 ||
			strcmp(fc_orgnum, "Infinity") == 0 ||
			strcmp(fc_orgnum, "-Infinity") == 0)
		{
			/*
			 * 允许6个字符用于前导符号、小数点、
			 * “e”、指数的符号和两个指数数字。
			 */
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 7);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 6);
			*fc_numstr = ' ';
			*(fc_numstr + fc_Num.pre + 1) = '.';
		}
		else if (*fc_orgnum != '-')
		{
			fc_numstr = (char *) palloc(strlen(fc_orgnum) + 2);
			*fc_numstr = ' ';
			strcpy(fc_numstr + 1, fc_orgnum);
		}
		else
		{
			fc_numstr = fc_orgnum;
		}
	}
	else
	{
		int			fc_numstr_pre_len;
		Numeric		fc_val = fc_value;

		if (IS_MULTI(&fc_Num))
		{
			Numeric		fc_a = int64_to_numeric(10);
			Numeric		fc_b = int64_to_numeric(fc_Num.multi);

			fc_x = DatumGetNumeric(DirectFunctionCall2(numeric_power,
													NumericGetDatum(fc_a),
													NumericGetDatum(fc_b)));
			fc_val = DatumGetNumeric(DirectFunctionCall2(numeric_mul,
													  NumericGetDatum(fc_value),
													  NumericGetDatum(fc_x)));
			fc_Num.pre += fc_Num.multi;
		}

		fc_x = DatumGetNumeric(DirectFunctionCall2(numeric_round,
												NumericGetDatum(fc_val),
												Int32GetDatum(fc_Num.post)));
		fc_orgnum = DatumGetCString(DirectFunctionCall1(numeric_out,
													 NumericGetDatum(fc_x)));

		if (*fc_orgnum == '-')
		{
			fc_sign = '-';
			fc_numstr = fc_orgnum + 1;
		}
		else
		{
			fc_sign = '+';
			fc_numstr = fc_orgnum;
		}

		if ((fc_p = strchr(fc_numstr, '.')))
			fc_numstr_pre_len = fc_p - fc_numstr;
		else
			fc_numstr_pre_len = strlen(fc_numstr);

		/* 需要填充吗？ */
		if (fc_numstr_pre_len < fc_Num.pre)
			fc_out_pre_spaces = fc_Num.pre - fc_numstr_pre_len;
		/* 超出了前缀数字格式？ */
		else if (fc_numstr_pre_len > fc_Num.pre)
		{
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 2);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 1);
			*(fc_numstr + fc_Num.pre) = '.';
		}
	}

	NUM_TOCHAR_finish;
	PG_RETURN_TEXT_P(fc_result);
}

/* ---------------
 * INT4 to_char()
 * ---------------
 */
Datum int4_to_char(PG_FUNCTION_ARGS)
{
	int32		fc_value = PG_GETARG_INT32(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	FormatNode *fc_format;
	text	   *fc_result;
	bool		fc_shouldFree;
	int			fc_out_pre_spaces = 0,
				fc_sign = 0;
	char	   *fc_numstr,
			   *fc_orgnum;

	NUM_TOCHAR_prepare;

	/*
	 * 在 DateType 依赖的部分 (int32)
	 */
	if (IS_ROMAN(&fc_Num))
		fc_numstr = fc_int_to_roman(fc_value);
	else if (IS_EEEE(&fc_Num))
	{
		/* 我们可以轻松做到，因为 float8 不会失去任何精度 */
		float8		fc_val = (float8) fc_value;

		fc_orgnum = (char *) psprintf("%+.*e", fc_Num.post, fc_val);

		/*
		 * 将前导正号替换为空格。
		 */
		if (*fc_orgnum == '+')
			*fc_orgnum = ' ';

		fc_numstr = fc_orgnum;
	}
	else
	{
		int			fc_numstr_pre_len;

		if (IS_MULTI(&fc_Num))
		{
			fc_orgnum = DatumGetCString(DirectFunctionCall1(int4out,
														 Int32GetDatum(fc_value * ((int32) pow((double) 10, (double) fc_Num.multi)))));
			fc_Num.pre += fc_Num.multi;
		}
		else
		{
			fc_orgnum = DatumGetCString(DirectFunctionCall1(int4out,
														 Int32GetDatum(fc_value)));
		}

		if (*fc_orgnum == '-')
		{
			fc_sign = '-';
			fc_orgnum++;
		}
		else
			fc_sign = '+';

		fc_numstr_pre_len = strlen(fc_orgnum);

		/* 后小数位数字？用零填充。 */
		if (fc_Num.post)
		{
			fc_numstr = (char *) palloc(fc_numstr_pre_len + fc_Num.post + 2);
			strcpy(fc_numstr, fc_orgnum);
			*(fc_numstr + fc_numstr_pre_len) = '.';
			memset(fc_numstr + fc_numstr_pre_len + 1, '0', fc_Num.post);
			*(fc_numstr + fc_numstr_pre_len + fc_Num.post + 1) = '\0';
		}
		else
			fc_numstr = fc_orgnum;

		/* 需要填充吗？ */
		if (fc_numstr_pre_len < fc_Num.pre)
			fc_out_pre_spaces = fc_Num.pre - fc_numstr_pre_len;
		/* 超出了前缀数字格式？ */
		else if (fc_numstr_pre_len > fc_Num.pre)
		{
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 2);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 1);
			*(fc_numstr + fc_Num.pre) = '.';
		}
	}

	NUM_TOCHAR_finish;
	PG_RETURN_TEXT_P(fc_result);
}

/* ---------------
 * INT8 to_char()
 * ---------------
 */
Datum int8_to_char(PG_FUNCTION_ARGS)
{
	int64		fc_value = PG_GETARG_INT64(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	FormatNode *fc_format;
	text	   *fc_result;
	bool		fc_shouldFree;
	int			fc_out_pre_spaces = 0,
				fc_sign = 0;
	char	   *fc_numstr,
			   *fc_orgnum;

	NUM_TOCHAR_prepare;

	/*
	 * 在 DateType 依赖的部分 (int32)
	 */
	if (IS_ROMAN(&fc_Num))
	{
		/* 目前不支持 int8 转换为罗马数字... */
		fc_numstr = fc_int_to_roman(DatumGetInt32(DirectFunctionCall1(int84, Int64GetDatum(fc_value))));
	}
	else if (IS_EEEE(&fc_Num))
	{
		/* 为了避免失去精度，必须通过 numeric 而不是 float8 */
		fc_orgnum = numeric_out_sci(int64_to_numeric(fc_value),
								 fc_Num.post);

		/*
		 * numeric_out_sci() 不会对正数发出符号。我们
		 * 需要在这种情况下添加一个空格，以便正数和负数
		 * 对齐。我们不需要担心 NaN/inf。
		 */
		if (*fc_orgnum != '-')
		{
			fc_numstr = (char *) palloc(strlen(fc_orgnum) + 2);
			*fc_numstr = ' ';
			strcpy(fc_numstr + 1, fc_orgnum);
		}
		else
		{
			fc_numstr = fc_orgnum;
		}
	}
	else
	{
		int			fc_numstr_pre_len;

		if (IS_MULTI(&fc_Num))
		{
			double		fc_multi = pow((double) 10, (double) fc_Num.multi);

			fc_value = DatumGetInt64(DirectFunctionCall2(int8mul,
													  Int64GetDatum(fc_value),
													  DirectFunctionCall1(dtoi8,
																		  Float8GetDatum(fc_multi))));
			fc_Num.pre += fc_Num.multi;
		}

		fc_orgnum = DatumGetCString(DirectFunctionCall1(int8out,
													 Int64GetDatum(fc_value)));

		if (*fc_orgnum == '-')
		{
			fc_sign = '-';
			fc_orgnum++;
		}
		else
			fc_sign = '+';

		fc_numstr_pre_len = strlen(fc_orgnum);

		/* 后小数位数字？用零填充。 */
		if (fc_Num.post)
		{
			fc_numstr = (char *) palloc(fc_numstr_pre_len + fc_Num.post + 2);
			strcpy(fc_numstr, fc_orgnum);
			*(fc_numstr + fc_numstr_pre_len) = '.';
			memset(fc_numstr + fc_numstr_pre_len + 1, '0', fc_Num.post);
			*(fc_numstr + fc_numstr_pre_len + fc_Num.post + 1) = '\0';
		}
		else
			fc_numstr = fc_orgnum;

		/* 需要填充吗？ */
		if (fc_numstr_pre_len < fc_Num.pre)
			fc_out_pre_spaces = fc_Num.pre - fc_numstr_pre_len;
		/* 超出了前缀数字格式？ */
		else if (fc_numstr_pre_len > fc_Num.pre)
		{
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 2);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 1);
			*(fc_numstr + fc_Num.pre) = '.';
		}
	}

	NUM_TOCHAR_finish;
	PG_RETURN_TEXT_P(fc_result);
}

/* -----------------
 * FLOAT4 to_char()
 * -----------------
 */
Datum float4_to_char(PG_FUNCTION_ARGS)
{
	float4		fc_value = PG_GETARG_FLOAT4(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	FormatNode *fc_format;
	text	   *fc_result;
	bool		fc_shouldFree;
	int			fc_out_pre_spaces = 0,
				fc_sign = 0;
	char	   *fc_numstr,
			   *fc_p;

	NUM_TOCHAR_prepare;

	if (IS_ROMAN(&fc_Num))
		fc_numstr = fc_int_to_roman((int) rint(fc_value));
	else if (IS_EEEE(&fc_Num))
	{
		if (isnan(fc_value) || isinf(fc_value))
		{
			/*
			 * 允许6个字符用于前导符号、小数点、
			 * “e”、指数的符号和两个指数数字。
			 */
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 7);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 6);
			*fc_numstr = ' ';
			*(fc_numstr + fc_Num.pre + 1) = '.';
		}
		else
		{
			fc_numstr = psprintf("%+.*e", fc_Num.post, fc_value);

			/*
			 * 将前导正号替换为空格。
			 */
			if (*fc_numstr == '+')
				*fc_numstr = ' ';
		}
	}
	else
	{
		float4		fc_val = fc_value;
		char	   *fc_orgnum;
		int			fc_numstr_pre_len;

		if (IS_MULTI(&fc_Num))
		{
			float		fc_multi = pow((double) 10, (double) fc_Num.multi);

			fc_val = fc_value * fc_multi;
			fc_Num.pre += fc_Num.multi;
		}

		fc_orgnum = psprintf("%.0f", fabs(fc_val));
		fc_numstr_pre_len = strlen(fc_orgnum);

		/* 调整后数字以适应最大浮点数字 */
		if (fc_numstr_pre_len >= FLT_DIG)
			fc_Num.post = 0;
		else if (fc_numstr_pre_len + fc_Num.post > FLT_DIG)
			fc_Num.post = FLT_DIG - fc_numstr_pre_len;
		fc_orgnum = psprintf("%.*f", fc_Num.post, fc_val);

		if (*fc_orgnum == '-')
		{						/* < 0 */
			fc_sign = '-';
			fc_numstr = fc_orgnum + 1;
		}
		else
		{
			fc_sign = '+';
			fc_numstr = fc_orgnum;
		}

		if ((fc_p = strchr(fc_numstr, '.')))
			fc_numstr_pre_len = fc_p - fc_numstr;
		else
			fc_numstr_pre_len = strlen(fc_numstr);

		/* 需要填充吗？ */
		if (fc_numstr_pre_len < fc_Num.pre)
			fc_out_pre_spaces = fc_Num.pre - fc_numstr_pre_len;
		/* 超出了前缀数字格式？ */
		else if (fc_numstr_pre_len > fc_Num.pre)
		{
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 2);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 1);
			*(fc_numstr + fc_Num.pre) = '.';
		}
	}

	NUM_TOCHAR_finish;
	PG_RETURN_TEXT_P(fc_result);
}

/* -----------------
 * FLOAT8 to_char()
 * -----------------
 */
Datum float8_to_char(PG_FUNCTION_ARGS)
{
	float8		fc_value = PG_GETARG_FLOAT8(0);
	text	   *fc_fmt = PG_GETARG_TEXT_PP(1);
	NUMDesc		fc_Num;
	FormatNode *fc_format;
	text	   *fc_result;
	bool		fc_shouldFree;
	int			fc_out_pre_spaces = 0,
				fc_sign = 0;
	char	   *fc_numstr,
			   *fc_p;

	NUM_TOCHAR_prepare;

	if (IS_ROMAN(&fc_Num))
		fc_numstr = fc_int_to_roman((int) rint(fc_value));
	else if (IS_EEEE(&fc_Num))
	{
		if (isnan(fc_value) || isinf(fc_value))
		{
			/*
			 * 允许6个字符用于前导符号、小数点、
			 * “e”、指数的符号和两个指数数字。
			 */
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 7);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 6);
			*fc_numstr = ' ';
			*(fc_numstr + fc_Num.pre + 1) = '.';
		}
		else
		{
			fc_numstr = psprintf("%+.*e", fc_Num.post, fc_value);

			/*
			 * 将前导正号替换为空格。
			 */
			if (*fc_numstr == '+')
				*fc_numstr = ' ';
		}
	}
	else
	{
		float8		fc_val = fc_value;
		char	   *fc_orgnum;
		int			fc_numstr_pre_len;

		if (IS_MULTI(&fc_Num))
		{
			double		fc_multi = pow((double) 10, (double) fc_Num.multi);

			fc_val = fc_value * fc_multi;
			fc_Num.pre += fc_Num.multi;
		}

		fc_orgnum = psprintf("%.0f", fabs(fc_val));
		fc_numstr_pre_len = strlen(fc_orgnum);

		/* 调整后数字以适应最大双精度数字 */
		if (fc_numstr_pre_len >= DBL_DIG)
			fc_Num.post = 0;
		else if (fc_numstr_pre_len + fc_Num.post > DBL_DIG)
			fc_Num.post = DBL_DIG - fc_numstr_pre_len;
		fc_orgnum = psprintf("%.*f", fc_Num.post, fc_val);

		if (*fc_orgnum == '-')
		{						/* < 0 */
			fc_sign = '-';
			fc_numstr = fc_orgnum + 1;
		}
		else
		{
			fc_sign = '+';
			fc_numstr = fc_orgnum;
		}

		if ((fc_p = strchr(fc_numstr, '.')))
			fc_numstr_pre_len = fc_p - fc_numstr;
		else
			fc_numstr_pre_len = strlen(fc_numstr);

		/* 需要填充吗？ */
		if (fc_numstr_pre_len < fc_Num.pre)
			fc_out_pre_spaces = fc_Num.pre - fc_numstr_pre_len;
		/* 超出了前缀数字格式？ */
		else if (fc_numstr_pre_len > fc_Num.pre)
		{
			fc_numstr = (char *) palloc(fc_Num.pre + fc_Num.post + 2);
			fc_fill_str(fc_numstr, '#', fc_Num.pre + fc_Num.post + 1);
			*(fc_numstr + fc_Num.pre) = '.';
		}
	}

	NUM_TOCHAR_finish;
	PG_RETURN_TEXT_P(fc_result);
}
